diff --git a/.editorconfig b/.editorconfig index 772e34f2..1525fadc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,6 +18,7 @@ ij_wrap_on_typing = false ij_css_align_closing_brace_with_properties = false ij_css_blank_lines_around_nested_selector = 1 ij_css_blank_lines_between_blocks = 1 +ij_css_block_comment_add_space = false ij_css_brace_placement = end_of_line ij_css_enforce_quotes_on_format = false ij_css_hex_color_long_format = false @@ -33,6 +34,10 @@ ij_css_space_before_opening_brace = true ij_css_use_double_quotes = true ij_css_value_alignment = do_not_align +[*.csv] +max_line_length = 2147483647 +ij_csv_wrap_long_lines = false + [*.feature] indent_size = 2 ij_gherkin_keep_indents_on_empty_lines = false @@ -86,7 +91,9 @@ ij_java_blank_lines_before_imports = 1 ij_java_blank_lines_before_method_body = 0 ij_java_blank_lines_before_package = 0 ij_java_block_brace_style = end_of_line +ij_java_block_comment_add_space = false ij_java_block_comment_at_first_column = true +ij_java_builder_methods = none ij_java_call_parameters_new_line_after_left_paren = false ij_java_call_parameters_right_paren_on_new_line = false ij_java_call_parameters_wrap = off @@ -146,6 +153,7 @@ ij_java_keep_blank_lines_before_right_brace = 2 ij_java_keep_blank_lines_between_package_declaration_and_header = 2 ij_java_keep_blank_lines_in_code = 2 ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false ij_java_keep_control_statement_in_one_line = true ij_java_keep_first_column_comment = true ij_java_keep_indents_on_empty_lines = false @@ -294,6 +302,7 @@ indent_size = 2 ij_less_align_closing_brace_with_properties = false ij_less_blank_lines_around_nested_selector = 1 ij_less_blank_lines_between_blocks = 1 +ij_less_block_comment_add_space = false ij_less_brace_placement = 0 ij_less_enforce_quotes_on_format = false ij_less_hex_color_long_format = false @@ -303,12 +312,27 @@ ij_less_hex_color_upper_case = false ij_less_keep_blank_lines_in_code = 2 ij_less_keep_indents_on_empty_lines = false ij_less_keep_single_line_blocks = false +ij_less_line_comment_add_space = false +ij_less_line_comment_at_first_column = false ij_less_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow ij_less_space_after_colon = true ij_less_space_before_opening_brace = true ij_less_use_double_quotes = true ij_less_value_alignment = 0 +[*.proto] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_protobuf_keep_blank_lines_in_code = 2 +ij_protobuf_keep_indents_on_empty_lines = false +ij_protobuf_keep_line_breaks = true +ij_protobuf_space_after_comma = true +ij_protobuf_space_before_comma = false +ij_protobuf_spaces_around_assignment_operators = true +ij_protobuf_spaces_within_braces = false +ij_protobuf_spaces_within_brackets = false + [*.sass] indent_size = 2 ij_sass_align_closing_brace_with_properties = false @@ -323,6 +347,8 @@ ij_sass_hex_color_upper_case = false ij_sass_keep_blank_lines_in_code = 2 ij_sass_keep_indents_on_empty_lines = false ij_sass_keep_single_line_blocks = false +ij_sass_line_comment_add_space = false +ij_sass_line_comment_at_first_column = false ij_sass_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow ij_sass_space_after_colon = true ij_sass_space_before_opening_brace = true @@ -334,6 +360,7 @@ indent_size = 2 ij_scss_align_closing_brace_with_properties = false ij_scss_blank_lines_around_nested_selector = 1 ij_scss_blank_lines_between_blocks = 1 +ij_scss_block_comment_add_space = false ij_scss_brace_placement = 0 ij_scss_enforce_quotes_on_format = false ij_scss_hex_color_long_format = false @@ -343,6 +370,8 @@ ij_scss_hex_color_upper_case = false ij_scss_keep_blank_lines_in_code = 2 ij_scss_keep_indents_on_empty_lines = false ij_scss_keep_single_line_blocks = false +ij_scss_line_comment_add_space = false +ij_scss_line_comment_at_first_column = false ij_scss_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow ij_scss_space_after_colon = true ij_scss_space_before_opening_brace = true @@ -377,14 +406,17 @@ ij_editorconfig_space_before_colon = false ij_editorconfig_space_before_comma = false ij_editorconfig_spaces_around_assignment_operators = true -[{*.ad, *.adoc, *.asciidoc, .asciidoctorconfig}] +[{*.ad,*.adoc,*.asciidoc,.asciidoctorconfig}] +ij_asciidoc_blank_lines_after_header = 1 +ij_asciidoc_blank_lines_keep_after_header = 1 ij_asciidoc_formatting_enabled = true -ij_asciidoc_one_sentence_per_line = false +ij_asciidoc_one_sentence_per_line = true -[{*.ant, *.fo, *.fxml, *.jhm, *.jnlp, *.jrxml, *.pom, *.rng, *.tld, *.wadl, *.wsdd, *.wsdl, *.xjb, *.xml, *.xsd, *.xsl, *.xslt, *.xul}] +[{*.ant,*.fo,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.qrc,*.rng,*.tld,*.wadl,*.wsdd,*.wsdl,*.xjb,*.xml,*.xsd,*.xsl,*.xslt,*.xul,phpunit.xml.dist}] ij_xml_align_attributes = true ij_xml_align_text = false ij_xml_attribute_wrap = normal +ij_xml_block_comment_add_space = false ij_xml_block_comment_at_first_column = true ij_xml_keep_blank_lines = 2 ij_xml_keep_indents_on_empty_lines = false @@ -399,7 +431,7 @@ ij_xml_space_around_equals_in_attribute = false ij_xml_space_inside_empty_tag = false ij_xml_text_wrap = normal -[{*.ats, *.ts}] +[{*.ats,*.cts,*.mts,*.ts}] ij_continuation_indent_size = 4 ij_typescript_align_imports = false ij_typescript_align_multiline_array_initializer_expression = false @@ -428,6 +460,8 @@ ij_typescript_blank_lines_around_function = 1 ij_typescript_blank_lines_around_method = 1 ij_typescript_blank_lines_around_method_in_interface = 1 ij_typescript_block_brace_style = end_of_line +ij_typescript_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true ij_typescript_call_parameters_new_line_after_left_paren = false ij_typescript_call_parameters_right_paren_on_new_line = false ij_typescript_call_parameters_wrap = off @@ -438,6 +472,7 @@ ij_typescript_comma_on_new_line = false ij_typescript_do_while_brace_force = never ij_typescript_else_on_new_line = false ij_typescript_enforce_trailing_comma = keep +ij_typescript_enum_constants_wrap = on_every_item ij_typescript_extends_keyword_wrap = off ij_typescript_extends_list_wrap = off ij_typescript_field_prefix = _ @@ -558,7 +593,7 @@ ij_typescript_ternary_operation_wrap = off ij_typescript_union_types_wrap = on_every_item ij_typescript_use_chained_calls_group_indents = false ij_typescript_use_double_quotes = true -ij_typescript_use_explicit_js_extension = global +ij_typescript_use_explicit_js_extension = auto ij_typescript_use_path_mapping = always ij_typescript_use_public_modifier = false ij_typescript_use_semicolon_after_statement = true @@ -567,7 +602,7 @@ ij_typescript_while_brace_force = never ij_typescript_while_on_new_line = false ij_typescript_wrap_comments = false -[{*.bash, *.sh, *.zsh}] +[{*.bash,*.sh,*.zsh}] indent_size = 2 tab_width = 2 ij_shell_binary_ops_start_line = false @@ -575,8 +610,9 @@ ij_shell_keep_column_alignment_padding = false ij_shell_minify_program = false ij_shell_redirect_followed_by_space = false ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true -[{*.cjs, *.js}] +[{*.cjs,*.js}] ij_continuation_indent_size = 4 ij_javascript_align_imports = false ij_javascript_align_multiline_array_initializer_expression = false @@ -603,6 +639,8 @@ ij_javascript_blank_lines_around_field = 0 ij_javascript_blank_lines_around_function = 1 ij_javascript_blank_lines_around_method = 1 ij_javascript_block_brace_style = end_of_line +ij_javascript_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true ij_javascript_call_parameters_new_line_after_left_paren = false ij_javascript_call_parameters_right_paren_on_new_line = false ij_javascript_call_parameters_wrap = off @@ -732,7 +770,7 @@ ij_javascript_ternary_operation_wrap = off ij_javascript_union_types_wrap = on_every_item ij_javascript_use_chained_calls_group_indents = false ij_javascript_use_double_quotes = true -ij_javascript_use_explicit_js_extension = global +ij_javascript_use_explicit_js_extension = auto ij_javascript_use_path_mapping = always ij_javascript_use_public_modifier = false ij_javascript_use_semicolon_after_statement = true @@ -741,107 +779,242 @@ ij_javascript_while_brace_force = never ij_javascript_while_on_new_line = false ij_javascript_wrap_comments = false -[{*.cjsx, *.coffee}] +[{*.cql,*.cyp,*.cypher}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_cypher_keep_indents_on_empty_lines = false + +[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}] +ij_continuation_indent_size = 4 +ij_php_align_assignments = false +ij_php_align_class_constants = false +ij_php_align_group_field_declarations = false +ij_php_align_inline_comments = false +ij_php_align_key_value_pairs = false +ij_php_align_match_arm_bodies = false +ij_php_align_multiline_array_initializer_expression = false +ij_php_align_multiline_binary_operation = false +ij_php_align_multiline_chained_methods = false +ij_php_align_multiline_extends_list = false +ij_php_align_multiline_for = true +ij_php_align_multiline_parameters = true +ij_php_align_multiline_parameters_in_calls = false +ij_php_align_multiline_ternary_operation = false +ij_php_align_named_arguments = false +ij_php_align_phpdoc_comments = false +ij_php_align_phpdoc_param_names = false +ij_php_anonymous_brace_style = end_of_line +ij_php_api_weight = 28 +ij_php_array_initializer_new_line_after_left_brace = false +ij_php_array_initializer_right_brace_on_new_line = false +ij_php_array_initializer_wrap = off +ij_php_assignment_wrap = off +ij_php_attributes_wrap = off +ij_php_author_weight = 28 +ij_php_binary_operation_sign_on_next_line = false +ij_php_binary_operation_wrap = off +ij_php_blank_lines_after_class_header = 0 +ij_php_blank_lines_after_function = 1 +ij_php_blank_lines_after_imports = 1 +ij_php_blank_lines_after_opening_tag = 0 +ij_php_blank_lines_after_package = 0 +ij_php_blank_lines_around_class = 1 +ij_php_blank_lines_around_constants = 0 +ij_php_blank_lines_around_field = 0 +ij_php_blank_lines_around_method = 1 +ij_php_blank_lines_before_class_end = 0 +ij_php_blank_lines_before_imports = 1 +ij_php_blank_lines_before_method_body = 0 +ij_php_blank_lines_before_package = 1 +ij_php_blank_lines_before_return_statement = 0 +ij_php_blank_lines_between_imports = 0 +ij_php_block_brace_style = end_of_line +ij_php_call_parameters_new_line_after_left_paren = false +ij_php_call_parameters_right_paren_on_new_line = false +ij_php_call_parameters_wrap = off +ij_php_catch_on_new_line = false +ij_php_category_weight = 28 +ij_php_class_brace_style = next_line +ij_php_comma_after_last_array_element = false +ij_php_concat_spaces = true +ij_php_copyright_weight = 28 +ij_php_deprecated_weight = 28 +ij_php_do_while_brace_force = never +ij_php_else_if_style = as_is +ij_php_else_on_new_line = false +ij_php_example_weight = 28 +ij_php_extends_keyword_wrap = off +ij_php_extends_list_wrap = off +ij_php_fields_default_visibility = private +ij_php_filesource_weight = 28 +ij_php_finally_on_new_line = false +ij_php_for_brace_force = never +ij_php_for_statement_new_line_after_left_paren = false +ij_php_for_statement_right_paren_on_new_line = false +ij_php_for_statement_wrap = off +ij_php_force_empty_methods_in_one_line = false +ij_php_force_short_declaration_array_style = false +ij_php_getters_setters_naming_style = camel_case +ij_php_getters_setters_order_style = getters_first +ij_php_global_weight = 28 +ij_php_group_use_wrap = on_every_item +ij_php_if_brace_force = never +ij_php_if_lparen_on_next_line = false +ij_php_if_rparen_on_next_line = false +ij_php_ignore_weight = 28 +ij_php_import_sorting = alphabetic +ij_php_indent_break_from_case = true +ij_php_indent_case_from_switch = true +ij_php_indent_code_in_php_tags = false +ij_php_internal_weight = 28 +ij_php_keep_blank_lines_after_lbrace = 2 +ij_php_keep_blank_lines_before_right_brace = 2 +ij_php_keep_blank_lines_in_code = 2 +ij_php_keep_blank_lines_in_declarations = 2 +ij_php_keep_control_statement_in_one_line = true +ij_php_keep_first_column_comment = true +ij_php_keep_indents_on_empty_lines = false +ij_php_keep_line_breaks = true +ij_php_keep_rparen_and_lbrace_on_one_line = false +ij_php_keep_simple_classes_in_one_line = false +ij_php_keep_simple_methods_in_one_line = false +ij_php_lambda_brace_style = end_of_line +ij_php_license_weight = 28 +ij_php_line_comment_add_space = false +ij_php_line_comment_at_first_column = true +ij_php_link_weight = 28 +ij_php_lower_case_boolean_const = false +ij_php_lower_case_keywords = true +ij_php_lower_case_null_const = false +ij_php_method_brace_style = next_line +ij_php_method_call_chain_wrap = off +ij_php_method_parameters_new_line_after_left_paren = false +ij_php_method_parameters_right_paren_on_new_line = false +ij_php_method_parameters_wrap = off +ij_php_method_weight = 28 +ij_php_modifier_list_wrap = false +ij_php_multiline_chained_calls_semicolon_on_new_line = false +ij_php_namespace_brace_style = 1 +ij_php_new_line_after_php_opening_tag = false +ij_php_null_type_position = in_the_end +ij_php_package_weight = 28 +ij_php_param_weight = 0 +ij_php_parameters_attributes_wrap = off +ij_php_parentheses_expression_new_line_after_left_paren = false +ij_php_parentheses_expression_right_paren_on_new_line = false +ij_php_phpdoc_blank_line_before_tags = false +ij_php_phpdoc_blank_lines_around_parameters = false +ij_php_phpdoc_keep_blank_lines = true +ij_php_phpdoc_param_spaces_between_name_and_description = 1 +ij_php_phpdoc_param_spaces_between_tag_and_type = 1 +ij_php_phpdoc_param_spaces_between_type_and_name = 1 +ij_php_phpdoc_use_fqcn = false +ij_php_phpdoc_wrap_long_lines = false +ij_php_place_assignment_sign_on_next_line = false +ij_php_place_parens_for_constructor = 0 +ij_php_property_read_weight = 28 +ij_php_property_weight = 28 +ij_php_property_write_weight = 28 +ij_php_return_type_on_new_line = false +ij_php_return_weight = 1 +ij_php_see_weight = 28 +ij_php_since_weight = 28 +ij_php_sort_phpdoc_elements = true +ij_php_space_after_colon = true +ij_php_space_after_colon_in_enum_backed_type = true +ij_php_space_after_colon_in_named_argument = true +ij_php_space_after_colon_in_return_type = true +ij_php_space_after_comma = true +ij_php_space_after_for_semicolon = true +ij_php_space_after_quest = true +ij_php_space_after_type_cast = false +ij_php_space_after_unary_not = false +ij_php_space_before_array_initializer_left_brace = false +ij_php_space_before_catch_keyword = true +ij_php_space_before_catch_left_brace = true +ij_php_space_before_catch_parentheses = true +ij_php_space_before_class_left_brace = true +ij_php_space_before_closure_left_parenthesis = true +ij_php_space_before_colon = true +ij_php_space_before_colon_in_enum_backed_type = false +ij_php_space_before_colon_in_named_argument = false +ij_php_space_before_colon_in_return_type = false +ij_php_space_before_comma = false +ij_php_space_before_do_left_brace = true +ij_php_space_before_else_keyword = true +ij_php_space_before_else_left_brace = true +ij_php_space_before_finally_keyword = true +ij_php_space_before_finally_left_brace = true +ij_php_space_before_for_left_brace = true +ij_php_space_before_for_parentheses = true +ij_php_space_before_for_semicolon = false +ij_php_space_before_if_left_brace = true +ij_php_space_before_if_parentheses = true +ij_php_space_before_method_call_parentheses = false +ij_php_space_before_method_left_brace = true +ij_php_space_before_method_parentheses = false +ij_php_space_before_quest = true +ij_php_space_before_short_closure_left_parenthesis = false +ij_php_space_before_switch_left_brace = true +ij_php_space_before_switch_parentheses = true +ij_php_space_before_try_left_brace = true +ij_php_space_before_unary_not = false +ij_php_space_before_while_keyword = true +ij_php_space_before_while_left_brace = true +ij_php_space_before_while_parentheses = true +ij_php_space_between_ternary_quest_and_colon = false +ij_php_spaces_around_additive_operators = true +ij_php_spaces_around_arrow = false +ij_php_spaces_around_assignment_in_declare = false +ij_php_spaces_around_assignment_operators = true +ij_php_spaces_around_bitwise_operators = true +ij_php_spaces_around_equality_operators = true +ij_php_spaces_around_logical_operators = true +ij_php_spaces_around_multiplicative_operators = true +ij_php_spaces_around_null_coalesce_operator = true +ij_php_spaces_around_pipe_in_union_type = false +ij_php_spaces_around_relational_operators = true +ij_php_spaces_around_shift_operators = true +ij_php_spaces_around_unary_operator = false +ij_php_spaces_around_var_within_brackets = false +ij_php_spaces_within_array_initializer_braces = false +ij_php_spaces_within_brackets = false +ij_php_spaces_within_catch_parentheses = false +ij_php_spaces_within_for_parentheses = false +ij_php_spaces_within_if_parentheses = false +ij_php_spaces_within_method_call_parentheses = false +ij_php_spaces_within_method_parentheses = false +ij_php_spaces_within_parentheses = false +ij_php_spaces_within_short_echo_tags = true +ij_php_spaces_within_switch_parentheses = false +ij_php_spaces_within_while_parentheses = false +ij_php_special_else_if_treatment = false +ij_php_subpackage_weight = 28 +ij_php_ternary_operation_signs_on_next_line = false +ij_php_ternary_operation_wrap = off +ij_php_throws_weight = 2 +ij_php_todo_weight = 28 +ij_php_unknown_tag_weight = 28 +ij_php_upper_case_boolean_const = false +ij_php_upper_case_null_const = false +ij_php_uses_weight = 28 +ij_php_var_weight = 28 +ij_php_variable_naming_style = mixed +ij_php_version_weight = 28 +ij_php_while_brace_force = never +ij_php_while_on_new_line = false + +[{*.erb,*.rhtml}] indent_size = 2 tab_width = 2 ij_continuation_indent_size = 2 -ij_coffeescript_align_function_body = false -ij_coffeescript_align_imports = false -ij_coffeescript_align_multiline_array_initializer_expression = true -ij_coffeescript_align_multiline_parameters = true -ij_coffeescript_align_multiline_parameters_in_calls = false -ij_coffeescript_align_object_properties = 0 -ij_coffeescript_align_union_types = false -ij_coffeescript_align_var_statements = 0 -ij_coffeescript_array_initializer_new_line_after_left_brace = false -ij_coffeescript_array_initializer_right_brace_on_new_line = false -ij_coffeescript_array_initializer_wrap = normal -ij_coffeescript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/** -ij_coffeescript_blank_lines_around_function = 1 -ij_coffeescript_call_parameters_new_line_after_left_paren = false -ij_coffeescript_call_parameters_right_paren_on_new_line = false -ij_coffeescript_call_parameters_wrap = normal -ij_coffeescript_chained_call_dot_on_new_line = true -ij_coffeescript_comma_on_new_line = false -ij_coffeescript_enforce_trailing_comma = keep -ij_coffeescript_field_prefix = _ -ij_coffeescript_file_name_style = relaxed -ij_coffeescript_force_quote_style = false -ij_coffeescript_force_semicolon_style = false -ij_coffeescript_function_expression_brace_style = end_of_line -ij_coffeescript_import_merge_members = global -ij_coffeescript_import_prefer_absolute_path = global -ij_coffeescript_import_sort_members = true -ij_coffeescript_import_sort_module_name = false -ij_coffeescript_import_use_node_resolution = true -ij_coffeescript_imports_wrap = on_every_item -ij_coffeescript_indent_chained_calls = true -ij_coffeescript_indent_package_children = 0 -ij_coffeescript_jsx_attribute_value = braces -ij_coffeescript_keep_blank_lines_in_code = 2 -ij_coffeescript_keep_first_column_comment = true -ij_coffeescript_keep_indents_on_empty_lines = false -ij_coffeescript_keep_line_breaks = true -ij_coffeescript_keep_simple_methods_in_one_line = false -ij_coffeescript_method_parameters_new_line_after_left_paren = false -ij_coffeescript_method_parameters_right_paren_on_new_line = false -ij_coffeescript_method_parameters_wrap = off -ij_coffeescript_object_literal_wrap = on_every_item -ij_coffeescript_prefer_as_type_cast = false -ij_coffeescript_prefer_explicit_types_function_expression_returns = false -ij_coffeescript_prefer_explicit_types_function_returns = false -ij_coffeescript_prefer_explicit_types_vars_fields = false -ij_coffeescript_reformat_c_style_comments = false -ij_coffeescript_space_after_comma = true -ij_coffeescript_space_after_dots_in_rest_parameter = false -ij_coffeescript_space_after_generator_mult = true -ij_coffeescript_space_after_property_colon = true -ij_coffeescript_space_after_type_colon = true -ij_coffeescript_space_after_unary_not = false -ij_coffeescript_space_before_async_arrow_lparen = true -ij_coffeescript_space_before_class_lbrace = true -ij_coffeescript_space_before_comma = false -ij_coffeescript_space_before_function_left_parenth = true -ij_coffeescript_space_before_generator_mult = false -ij_coffeescript_space_before_property_colon = false -ij_coffeescript_space_before_type_colon = false -ij_coffeescript_space_before_unary_not = false -ij_coffeescript_spaces_around_additive_operators = true -ij_coffeescript_spaces_around_arrow_function_operator = true -ij_coffeescript_spaces_around_assignment_operators = true -ij_coffeescript_spaces_around_bitwise_operators = true -ij_coffeescript_spaces_around_equality_operators = true -ij_coffeescript_spaces_around_logical_operators = true -ij_coffeescript_spaces_around_multiplicative_operators = true -ij_coffeescript_spaces_around_relational_operators = true -ij_coffeescript_spaces_around_shift_operators = true -ij_coffeescript_spaces_around_unary_operator = false -ij_coffeescript_spaces_within_array_initializer_braces = false -ij_coffeescript_spaces_within_array_initializer_brackets = false -ij_coffeescript_spaces_within_imports = false -ij_coffeescript_spaces_within_index_brackets = false -ij_coffeescript_spaces_within_interpolation_expressions = false -ij_coffeescript_spaces_within_method_call_parentheses = false -ij_coffeescript_spaces_within_method_parentheses = false -ij_coffeescript_spaces_within_object_braces = false -ij_coffeescript_spaces_within_object_literal_braces = false -ij_coffeescript_spaces_within_object_type_braces = true -ij_coffeescript_spaces_within_range_brackets = false -ij_coffeescript_spaces_within_type_assertion = false -ij_coffeescript_spaces_within_union_types = true -ij_coffeescript_union_types_wrap = on_every_item -ij_coffeescript_use_chained_calls_group_indents = false -ij_coffeescript_use_double_quotes = true -ij_coffeescript_use_explicit_js_extension = global -ij_coffeescript_use_path_mapping = always -ij_coffeescript_use_public_modifier = false -ij_coffeescript_use_semicolon_after_statement = false -ij_coffeescript_var_declaration_wrap = normal +ij_rhtml_keep_indents_on_empty_lines = false -[{*.ft, *.vm, *.vsl}] +[{*.ft,*.vm,*.vsl}] ij_vtl_keep_indents_on_empty_lines = false -[{*.gant, *.gradle, *.groovy, *.gson, *.gy}] +[{*.gant,*.groovy,*.gson,*.gy}] ij_groovy_align_group_field_declarations = false ij_groovy_align_multiline_array_initializer_expression = false ij_groovy_align_multiline_assignment = false @@ -876,6 +1049,7 @@ ij_groovy_blank_lines_before_imports = 1 ij_groovy_blank_lines_before_method_body = 0 ij_groovy_blank_lines_before_package = 0 ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_add_space = false ij_groovy_block_comment_at_first_column = true ij_groovy_call_parameters_new_line_after_left_paren = false ij_groovy_call_parameters_right_paren_on_new_line = false @@ -927,6 +1101,7 @@ ij_groovy_method_parameters_right_paren_on_new_line = false ij_groovy_method_parameters_wrap = off ij_groovy_modifier_list_wrap = false ij_groovy_names_count_to_use_import_on_demand = 3 +ij_groovy_packages_to_use_import_on_demand = java.awt.*, javax.swing.* ij_groovy_parameter_annotation_wrap = off ij_groovy_parentheses_expression_new_line_after_left_paren = false ij_groovy_parentheses_expression_right_paren_on_new_line = false @@ -965,6 +1140,7 @@ ij_groovy_space_before_method_call_parentheses = false ij_groovy_space_before_method_left_brace = true ij_groovy_space_before_method_parentheses = false ij_groovy_space_before_quest = true +ij_groovy_space_before_record_parentheses = false ij_groovy_space_before_switch_left_brace = true ij_groovy_space_before_switch_parentheses = true ij_groovy_space_before_synchronized_left_brace = true @@ -1018,9 +1194,68 @@ ij_groovy_use_single_class_imports = true ij_groovy_variable_annotation_wrap = off ij_groovy_while_brace_force = never ij_groovy_while_on_new_line = false +ij_groovy_wrap_chain_calls_after_dot = false ij_groovy_wrap_long_lines = false -[{*.gradle.kts, *.kt, *.kts, *.main.kts}] +[{*.gemspec,*.jbuilder,*.rake,*.rb,*.rbi,*.rbw,*.ru,*.thor,.simplecov,capfile,cucumber,gemfile,guardfile,isolate,rails,rake,rakefile,rcov,spec,spork,steepfile,vagrantfile}] +ij_ruby_align_group_field_declarations = false +ij_ruby_align_multiline_parameters = true +ij_ruby_blank_lines_around_class = 1 +ij_ruby_blank_lines_around_method = 1 +ij_ruby_chain_calls_alignment = 2 +ij_ruby_convert_brace_block_by_enter = true +ij_ruby_empty_declarations_style = 1 +ij_ruby_force_newlines_around_visibility_mods = true +ij_ruby_indent_private_methods = false +ij_ruby_indent_protected_methods = false +ij_ruby_indent_public_methods = false +ij_ruby_indent_when_cases = false +ij_ruby_keep_blank_lines_in_code = 1 +ij_ruby_keep_blank_lines_in_declarations = 1 +ij_ruby_keep_line_breaks = true +ij_ruby_parentheses_around_method_arguments = true +ij_ruby_spaces_around_assignment_operators = true +ij_ruby_spaces_around_hashrocket = true +ij_ruby_spaces_around_other_operators = true +ij_ruby_spaces_around_range_operators = false +ij_ruby_spaces_around_relational_operators = true +ij_ruby_spaces_within_array_initializer_braces = true +ij_ruby_spaces_within_braces = true + +[{*.go,*.go2}] +indent_style = tab +ij_continuation_indent_size = 4 +ij_go_GROUP_CURRENT_PROJECT_IMPORTS = false +ij_go_add_leading_space_to_comments = false +ij_go_add_parentheses_for_single_import = false +ij_go_call_parameters_new_line_after_left_paren = true +ij_go_call_parameters_right_paren_on_new_line = true +ij_go_call_parameters_wrap = off +ij_go_fill_paragraph_width = 80 +ij_go_group_stdlib_imports = false +ij_go_import_sorting = gofmt +ij_go_keep_indents_on_empty_lines = false +ij_go_local_group_mode = project +ij_go_move_all_imports_in_one_declaration = false +ij_go_move_all_stdlib_imports_in_one_group = false +ij_go_remove_redundant_import_aliases = false +ij_go_run_go_fmt_on_reformat = true +ij_go_use_back_quotes_for_imports = false +ij_go_wrap_comp_lit = off +ij_go_wrap_comp_lit_newline_after_lbrace = true +ij_go_wrap_comp_lit_newline_before_rbrace = true +ij_go_wrap_func_params = off +ij_go_wrap_func_params_newline_after_lparen = true +ij_go_wrap_func_params_newline_before_rparen = true +ij_go_wrap_func_result = off +ij_go_wrap_func_result_newline_after_lparen = true +ij_go_wrap_func_result_newline_before_rparen = true + +[{*.gql,*.graphql,*.graphqls}] +indent_size = 2 +tab_width = 2 + +[{*.gradle.kts,*.kt,*.kts,*.main.kts,*.space.kts}] ij_kotlin_align_in_columns_case_branch = false ij_kotlin_align_multiline_binary_operation = false ij_kotlin_align_multiline_extends_list = false @@ -1029,29 +1264,31 @@ ij_kotlin_align_multiline_parameters = false ij_kotlin_align_multiline_parameters_in_calls = false ij_kotlin_allow_trailing_comma = false ij_kotlin_allow_trailing_comma_on_call_site = false -ij_kotlin_assignment_wrap = off +ij_kotlin_assignment_wrap = normal ij_kotlin_blank_lines_after_class_header = 0 ij_kotlin_blank_lines_around_block_when_branches = 0 ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_add_space = false ij_kotlin_block_comment_at_first_column = true -ij_kotlin_call_parameters_new_line_after_left_paren = false -ij_kotlin_call_parameters_right_paren_on_new_line = false -ij_kotlin_call_parameters_wrap = off +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = true +ij_kotlin_call_parameters_wrap = on_every_item ij_kotlin_catch_on_new_line = false ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL ij_kotlin_continuation_indent_for_chained_calls = false -ij_kotlin_continuation_indent_for_expression_bodies = true -ij_kotlin_continuation_indent_in_argument_lists = true -ij_kotlin_continuation_indent_in_elvis = true +ij_kotlin_continuation_indent_for_expression_bodies = false +ij_kotlin_continuation_indent_in_argument_lists = false +ij_kotlin_continuation_indent_in_elvis = false ij_kotlin_continuation_indent_in_if_conditions = false -ij_kotlin_continuation_indent_in_parameter_lists = true -ij_kotlin_continuation_indent_in_supertype_lists = true +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false ij_kotlin_else_on_new_line = false ij_kotlin_enum_constants_wrap = off -ij_kotlin_extends_list_wrap = off +ij_kotlin_extends_list_wrap = normal ij_kotlin_field_annotation_wrap = split_into_lines ij_kotlin_finally_on_new_line = false -ij_kotlin_if_rparen_on_new_line = false +ij_kotlin_if_rparen_on_new_line = true ij_kotlin_import_nested_classes = false ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^ ij_kotlin_insert_whitespaces_in_simple_one_line_method = true @@ -1065,10 +1302,10 @@ ij_kotlin_lbrace_on_next_line = false ij_kotlin_line_comment_add_space = false ij_kotlin_line_comment_at_first_column = true ij_kotlin_method_annotation_wrap = split_into_lines -ij_kotlin_method_call_chain_wrap = off -ij_kotlin_method_parameters_new_line_after_left_paren = false -ij_kotlin_method_parameters_right_paren_on_new_line = false -ij_kotlin_method_parameters_wrap = off +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item ij_kotlin_name_count_to_use_star_import = 5 ij_kotlin_name_count_to_use_star_import_for_members = 3 ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**, io.ktor.** @@ -1098,10 +1335,10 @@ ij_kotlin_spaces_around_when_arrow = true ij_kotlin_variable_annotation_wrap = off ij_kotlin_while_on_new_line = false ij_kotlin_wrap_elvis_expressions = 1 -ij_kotlin_wrap_expression_body_functions = 0 +ij_kotlin_wrap_expression_body_functions = 1 ij_kotlin_wrap_first_method_in_call_chain = false -[{*.graphqlconfig, *.graphql, *.har, *.jsb2, *.jsb3, *.json, .babelrc, .eslintrc, .stylelintrc, bowerrc, jest.config}] +[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,composer.lock,jest.config}] indent_size = 2 ij_json_keep_blank_lines_in_code = 0 ij_json_keep_indents_on_empty_lines = false @@ -1114,11 +1351,12 @@ ij_json_spaces_within_braces = false ij_json_spaces_within_brackets = false ij_json_wrap_long_lines = false -[{*.htm, *.html, *.ng, *.sht, *.shtm, *.shtml}] +[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}] ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3 ij_html_align_attributes = true ij_html_align_text = false ij_html_attribute_wrap = normal +ij_html_block_comment_add_space = false ij_html_block_comment_at_first_column = true ij_html_do_not_align_children_of_min_lines = 0 ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p @@ -1140,16 +1378,15 @@ ij_html_space_after_tag_name = false ij_html_space_around_equality_in_attribute = false ij_html_space_inside_empty_tag = false ij_html_text_wrap = normal -ij_html_uniform_ident = false -[{*.jsf, *.jsp, *.jspf, *.tag, *.tagf, *.xjsp}] +[{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}] ij_jsp_jsp_prefer_comma_separated_import_list = false ij_jsp_keep_indents_on_empty_lines = false -[{*.jspx, *.tagx}] +[{*.jspx,*.tagx}] ij_jspx_keep_indents_on_empty_lines = false -[{*.markdown, *.md}] +[{*.markdown,*.md}] ij_markdown_force_one_space_after_blockquote_symbol = true ij_markdown_force_one_space_after_header_symbol = true ij_markdown_force_one_space_after_list_bullet = true @@ -1162,16 +1399,110 @@ ij_markdown_min_lines_around_block_elements = 1 ij_markdown_min_lines_around_header = 1 ij_markdown_min_lines_between_paragraphs = 1 -[{*.properties, spring.handlers, spring.schemas}] +[{*.pb,*.textproto}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_prototext_keep_blank_lines_in_code = 2 +ij_prototext_keep_indents_on_empty_lines = false +ij_prototext_keep_line_breaks = true +ij_prototext_space_after_colon = true +ij_prototext_space_after_comma = true +ij_prototext_space_before_colon = false +ij_prototext_space_before_comma = false +ij_prototext_spaces_within_braces = true +ij_prototext_spaces_within_brackets = false + +[{*.properties,spring.handlers,spring.schemas}] ij_properties_align_group_field_declarations = false ij_properties_keep_blank_lines = false ij_properties_key_value_delimiter = equals ij_properties_spaces_around_key_value_delimiter = false -[{*.yaml, *.yml}] +[{*.py,*.pyw}] +ij_python_align_collections_and_comprehensions = true +ij_python_align_multiline_imports = true +ij_python_align_multiline_parameters = true +ij_python_align_multiline_parameters_in_calls = true +ij_python_blank_line_at_file_end = true +ij_python_blank_lines_after_imports = 1 +ij_python_blank_lines_after_local_imports = 0 +ij_python_blank_lines_around_class = 1 +ij_python_blank_lines_around_method = 1 +ij_python_blank_lines_around_top_level_classes_functions = 2 +ij_python_blank_lines_before_first_method = 0 +ij_python_call_parameters_new_line_after_left_paren = false +ij_python_call_parameters_right_paren_on_new_line = false +ij_python_call_parameters_wrap = normal +ij_python_dict_alignment = 0 +ij_python_dict_new_line_after_left_brace = false +ij_python_dict_new_line_before_right_brace = false +ij_python_dict_wrapping = 1 +ij_python_from_import_new_line_after_left_parenthesis = false +ij_python_from_import_new_line_before_right_parenthesis = false +ij_python_from_import_parentheses_force_if_multiline = false +ij_python_from_import_trailing_comma_if_multiline = false +ij_python_from_import_wrapping = 1 +ij_python_hang_closing_brackets = false +ij_python_keep_blank_lines_in_code = 1 +ij_python_keep_blank_lines_in_declarations = 1 +ij_python_keep_indents_on_empty_lines = false +ij_python_keep_line_breaks = true +ij_python_method_parameters_new_line_after_left_paren = false +ij_python_method_parameters_right_paren_on_new_line = false +ij_python_method_parameters_wrap = normal +ij_python_new_line_after_colon = false +ij_python_new_line_after_colon_multi_clause = true +ij_python_optimize_imports_always_split_from_imports = false +ij_python_optimize_imports_case_insensitive_order = false +ij_python_optimize_imports_join_from_imports_with_same_source = false +ij_python_optimize_imports_sort_by_type_first = true +ij_python_optimize_imports_sort_imports = true +ij_python_optimize_imports_sort_names_in_from_imports = false +ij_python_space_after_comma = true +ij_python_space_after_number_sign = true +ij_python_space_after_py_colon = true +ij_python_space_before_backslash = true +ij_python_space_before_comma = false +ij_python_space_before_for_semicolon = false +ij_python_space_before_lbracket = false +ij_python_space_before_method_call_parentheses = false +ij_python_space_before_method_parentheses = false +ij_python_space_before_number_sign = true +ij_python_space_before_py_colon = false +ij_python_space_within_empty_method_call_parentheses = false +ij_python_space_within_empty_method_parentheses = false +ij_python_spaces_around_additive_operators = true +ij_python_spaces_around_assignment_operators = true +ij_python_spaces_around_bitwise_operators = true +ij_python_spaces_around_eq_in_keyword_argument = false +ij_python_spaces_around_eq_in_named_parameter = false +ij_python_spaces_around_equality_operators = true +ij_python_spaces_around_multiplicative_operators = true +ij_python_spaces_around_power_operator = true +ij_python_spaces_around_relational_operators = true +ij_python_spaces_around_shift_operators = true +ij_python_spaces_within_braces = false +ij_python_spaces_within_brackets = false +ij_python_spaces_within_method_call_parentheses = false +ij_python_spaces_within_method_parentheses = false +ij_python_use_continuation_indent_for_arguments = false +ij_python_use_continuation_indent_for_collection_and_comprehensions = false +ij_python_use_continuation_indent_for_parameters = true +ij_python_wrap_long_lines = false + +[{*.qute.html,*.qute.json,*.qute.txt,*.qute.yaml,*.qute.yml}] +ij_qute_keep_indents_on_empty_lines = false + +[{*.yaml,*.yml}] indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true ij_yaml_keep_indents_on_empty_lines = false ij_yaml_keep_line_breaks = true -ij_yaml_space_before_colon = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false ij_yaml_spaces_within_braces = true ij_yaml_spaces_within_brackets = true diff --git a/.github/workflows/changelog-configuration.json b/.github/workflows/changelog-configuration.json index e21114e0..07b547b2 100644 --- a/.github/workflows/changelog-configuration.json +++ b/.github/workflows/changelog-configuration.json @@ -1,46 +1,59 @@ { - "max_tags_to_fetch": 200, - "max_pull_requests": 200, - "max_back_track_time_days": 365, - "exclude_merge_branches": [], - "sort": "ASC", - "template": "${{CHANGELOG}}", - "pr_template": "* ${{TITLE}} by @${{AUTHOR}} (#${{NUMBER}})", - "empty_template": "- no changes", - "categories": [ + "max_tags_to_fetch" : 200, + "max_pull_requests" : 200, + "max_back_track_time_days" : 365, + "exclude_merge_branches" : [], + "sort" : "ASC", + "template" : "${{CHANGELOG}}", + "pr_template" : "* ${{TITLE}} by @${{AUTHOR}} (#${{NUMBER}})", + "empty_template" : "- no changes", + "categories" : [ { - "title": "## Features", - "labels": ["feature", "enhancement"] + "title" : "## Features", + "labels" : [ + "feature", + "enhancement" + ] }, { - "title": "## API-Alignment\n\nAdjust API to match as much as possible to the one of [@neo4j/graphql](https://github.com/neo4j/graphql)", - "labels": ["API-Alignment"] + "title" : "## API-Alignment\n\nAdjust API to match as much as possible to the one of [@neo4j/graphql](https://github.com/neo4j/graphql)", + "labels" : [ + "API-Alignment" + ] }, { - "title": "## Fixes", - "labels": ["fix"] + "title" : "## Fixes", + "labels" : [ + "fix" + ] }, { - "title": "## Documentation", - "labels": ["doc"] + "title" : "## Documentation", + "labels" : [ + "doc" + ] }, { - "title": "## Tests", - "labels": ["test"] + "title" : "## Tests", + "labels" : [ + "test" + ] }, { - "title": "## Updated dependencies", - "labels": ["dependencies"] + "title" : "## Updated dependencies", + "labels" : [ + "dependencies" + ] } ], - "ignore_labels": [ + "ignore_labels" : [ "ignore" ], - "label_extractor": [ ], - "duplicate_filter": null, - "transformers": [], - "tag_resolver": { - "method": "semver" + "label_extractor" : [], + "duplicate_filter" : null, + "transformers" : [], + "tag_resolver" : { + "method" : "semver" }, - "base_branches": [] + "base_branches" : [] } diff --git a/.github/workflows/pr-build.yaml b/.github/workflows/pr-build.yaml index 731a8ac1..3225f9d9 100644 --- a/.github/workflows/pr-build.yaml +++ b/.github/workflows/pr-build.yaml @@ -20,7 +20,7 @@ jobs: java-version: 17 distribution: adopt - name: Run Maven build - run: ./mvnw --no-transfer-progress -Dneo4j-graphql-java.integration-tests=true -Dneo4j-graphql-java.generate-test-file-diff=false -Dneo4j-graphql-java.flatten-tests=true clean compile test + run: ./mvnw --no-transfer-progress -Dneo4j-graphql-java.integration-tests=true -Dneo4j-graphql-java.generate-test-file-diff=false -Dneo4j-graphql-java.flatten-tests=true clean compile test - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1 if: always() diff --git a/.github/workflows/release-changelog.yaml b/.github/workflows/release-changelog.yaml index 0ef42c96..ae89eb66 100644 --- a/.github/workflows/release-changelog.yaml +++ b/.github/workflows/release-changelog.yaml @@ -11,15 +11,15 @@ jobs: steps: - name: Check out repository code uses: actions/checkout@v2 - with : - fetch-depth : 0 - - name : Build Changelog - id : build_changelog - uses : mikepenz/release-changelog-builder-action@v1 - with : - configuration : ".github/workflows/changelog-configuration.json" - env : - GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }} + with: + fetch-depth: 0 + - name: Build Changelog + id: build_changelog + uses: mikepenz/release-changelog-builder-action@v1 + with: + configuration: ".github/workflows/changelog-configuration.json" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: update release id: update_release uses: tubone24/update_release@v1.2.0 diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index b901097f..fa827660 100644 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import java.net.*; import java.io.*; import java.nio.channels.*; @@ -25,7 +26,7 @@ public class MavenWrapperDownloader { * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; /** * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to @@ -54,7 +55,7 @@ public static void main(String args[]) { // wrapperUrl parameter. File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { + if (mavenWrapperPropertyFile.exists()) { FileInputStream mavenWrapperPropertyFileInputStream = null; try { mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); @@ -65,7 +66,7 @@ public static void main(String args[]) { System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); } finally { try { - if(mavenWrapperPropertyFileInputStream != null) { + if (mavenWrapperPropertyFileInputStream != null) { mavenWrapperPropertyFileInputStream.close(); } } catch (IOException e) { @@ -76,8 +77,8 @@ public static void main(String args[]) { System.out.println("- Downloading from: " + url); File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { System.out.println( "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); } diff --git a/core/src/main/kotlin/org/neo4j/graphql/AugmentationHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/AugmentationHandler.kt index 2e512681..0f9ef3ac 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/AugmentationHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/AugmentationHandler.kt @@ -15,9 +15,9 @@ import org.neo4j.graphql.handler.projection.ProjectionBase * but can also be a user specified query / mutation field */ abstract class AugmentationHandler( - val schemaConfig: SchemaConfig, - val typeDefinitionRegistry: TypeDefinitionRegistry, - val neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + val schemaConfig: SchemaConfig, + val typeDefinitionRegistry: TypeDefinitionRegistry, + val neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) { enum class OperationType { QUERY, @@ -40,11 +40,11 @@ abstract class AugmentationHandler( abstract fun createDataFetcher(operationType: OperationType, fieldDefinition: FieldDefinition): DataFetcher? protected fun buildFieldDefinition( - prefix: String, - resultType: ImplementingTypeDefinition<*>, - scalarFields: List, - nullableResult: Boolean, - forceOptionalProvider: (field: FieldDefinition) -> Boolean = { false } + prefix: String, + resultType: ImplementingTypeDefinition<*>, + scalarFields: List, + nullableResult: Boolean, + forceOptionalProvider: (field: FieldDefinition) -> Boolean = { false } ): FieldDefinition.Builder { var type: Type<*> = TypeName(resultType.name) if (!nullableResult) { @@ -57,9 +57,10 @@ abstract class AugmentationHandler( } protected fun getInputValueDefinitions( - relevantFields: List, - addFieldOperations: Boolean, - forceOptionalProvider: (field: FieldDefinition) -> Boolean): List { + relevantFields: List, + addFieldOperations: Boolean, + forceOptionalProvider: (field: FieldDefinition) -> Boolean + ): List { return relevantFields.flatMap { field -> var type = getInputType(field.type) type = if (forceOptionalProvider(field)) { @@ -69,7 +70,7 @@ abstract class AugmentationHandler( } if (addFieldOperations && !field.isNativeId()) { val typeDefinition = field.type.resolve() - ?: throw IllegalArgumentException("type ${field.type.name()} cannot be resolved") + ?: throw IllegalArgumentException("type ${field.type.name()} cannot be resolved") FieldOperator.forType(typeDefinition, field.type.inner().isNeo4jType(), field.type.isList()) .map { op -> val wrappedType: Type<*> = when { @@ -104,13 +105,15 @@ abstract class AugmentationHandler( private fun addOperation(rootTypeName: String, fieldDefinition: FieldDefinition) { val rootType = typeDefinitionRegistry.getType(rootTypeName)?.unwrap() if (rootType == null) { - typeDefinitionRegistry.add(ObjectTypeDefinition.newObjectTypeDefinition() - .name(rootTypeName) - .fieldDefinition(fieldDefinition) - .build()) + typeDefinitionRegistry.add( + ObjectTypeDefinition.newObjectTypeDefinition() + .name(rootTypeName) + .fieldDefinition(fieldDefinition) + .build() + ) } else { val existingRootType = (rootType as? ObjectTypeDefinition - ?: throw IllegalStateException("root type $rootTypeName is not an object type but ${rootType.javaClass}")) + ?: throw IllegalStateException("root type $rootTypeName is not an object type but ${rootType.javaClass}")) if (existingRootType.fieldDefinitions.find { it.name == fieldDefinition.name } != null) { return // definition already exists, we don't override it } @@ -119,7 +122,10 @@ abstract class AugmentationHandler( } } - protected fun addFilterType(type: ImplementingTypeDefinition<*>, createdTypes: MutableSet = mutableSetOf()): String { + protected fun addFilterType( + type: ImplementingTypeDefinition<*>, + createdTypes: MutableSet = mutableSetOf() + ): String { val filterName = if (schemaConfig.useWhereFilter) type.name + "Where" else "_${type.name}Filter" if (createdTypes.contains(filterName)) { return filterName @@ -127,23 +133,25 @@ abstract class AugmentationHandler( val existingFilterType = typeDefinitionRegistry.getType(filterName).unwrap() if (existingFilterType != null) { return (existingFilterType as? InputObjectTypeDefinition)?.name - ?: throw IllegalStateException("Filter type $filterName is already defined but not an input type") + ?: throw IllegalStateException("Filter type $filterName is already defined but not an input type") } createdTypes.add(filterName) val builder = InputObjectTypeDefinition.newInputObjectDefinition() .name(filterName) listOf("AND", "OR", "NOT").forEach { - builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition() - .name(it) - .type(ListType(NonNullType(TypeName(filterName)))) - .build()) + builder.inputValueDefinition( + InputValueDefinition.newInputValueDefinition() + .name(it) + .type(ListType(NonNullType(TypeName(filterName)))) + .build() + ) } type.fieldDefinitions .filterNot { it.isIgnored() } .filter { it.dynamicPrefix() == null } // TODO currently we do not support filtering on dynamic properties .forEach { field -> val typeDefinition = field.type.resolve() - ?: throw IllegalArgumentException("type ${field.type.name()} cannot be resolved") + ?: throw IllegalArgumentException("type ${field.type.name()} cannot be resolved") val filterType = when { field.type.inner().isNeo4jType() -> getInputType(field.type).name()!! typeDefinition is ScalarTypeDefinition -> typeDefinition.name @@ -154,6 +162,7 @@ abstract class AugmentationHandler( else -> addFilterType(typeDefinition, createdTypes) } } + else -> throw IllegalArgumentException("${field.type.name()} is neither an object nor an interface") } @@ -161,14 +170,33 @@ abstract class AugmentationHandler( RelationOperator.createRelationFilterFields(type, field, filterType, builder) } else { FieldOperator.forType(typeDefinition, field.type.inner().isNeo4jType(), field.type.isList()) - .forEach { op -> when { - field.type.isList() -> builder.addArrayFilterField(op.fieldName(field.name), filterType, field.description) - else -> builder.addFilterField(op.fieldName(field.name), op.list, filterType, field.description) - }} + .forEach { op -> + when { + field.type.isList() -> builder.addArrayFilterField( + op.fieldName(field.name), + filterType, + field.description + ) + + else -> builder.addFilterField( + op.fieldName(field.name), + op.list, + filterType, + field.description + ) + } + } if (typeDefinition.isNeo4jSpatialType()) { - val distanceFilterType = getSpatialDistanceFilter(neo4jTypeDefinitionRegistry.getUnwrappedType(filterType) as TypeDefinition<*>) + val distanceFilterType = + getSpatialDistanceFilter(neo4jTypeDefinitionRegistry.getUnwrappedType(filterType) as TypeDefinition<*>) FieldOperator.forType(distanceFilterType, true, field.type.isList()) - .forEach { op -> builder.addFilterField(op.fieldName(field.name + NEO4j_POINT_DISTANCE_FILTER_SUFFIX), op.list, NEO4j_POINT_DISTANCE_FILTER) } + .forEach { op -> + builder.addFilterField( + op.fieldName(field.name + NEO4j_POINT_DISTANCE_FILTER_SUFFIX), + op.list, + NEO4j_POINT_DISTANCE_FILTER + ) + } } } @@ -178,10 +206,12 @@ abstract class AugmentationHandler( } private fun getSpatialDistanceFilter(pointType: TypeDefinition<*>): InputObjectTypeDefinition { - return addInputType(NEO4j_POINT_DISTANCE_FILTER, listOf( + return addInputType( + NEO4j_POINT_DISTANCE_FILTER, listOf( input("distance", NonNullType(TypeFloat)), input("point", NonNullType(TypeName(pointType.name))) - )) + ) + ) } protected fun addOptions(type: ImplementingTypeDefinition<*>): String { @@ -189,19 +219,27 @@ abstract class AugmentationHandler( val optionsType = typeDefinitionRegistry.getType(optionsName)?.unwrap() if (optionsType != null) { return (optionsType as? InputObjectTypeDefinition)?.name - ?: throw IllegalStateException("Ordering type $type.name is already defined but not an input type") + ?: throw IllegalStateException("Ordering type $type.name is already defined but not an input type") } val sortTypeName = addSortInputType(type) val optionsTypeBuilder = InputObjectTypeDefinition.newInputObjectDefinition().name(optionsName) if (sortTypeName != null) { - optionsTypeBuilder.inputValueDefinition(input( + optionsTypeBuilder.inputValueDefinition( + input( ProjectionBase.SORT, ListType(NonNullType(TypeName(sortTypeName))), - "Specify one or more $sortTypeName objects to sort ${English.plural(type.name)} by. The sorts will be applied in the order in which they are arranged in the array.") + "Specify one or more $sortTypeName objects to sort ${English.plural(type.name)} by. The sorts will be applied in the order in which they are arranged in the array." + ) ) } optionsTypeBuilder - .inputValueDefinition(input(ProjectionBase.LIMIT, TypeInt, "Defines the maximum amount of records returned")) + .inputValueDefinition( + input( + ProjectionBase.LIMIT, + TypeInt, + "Defines the maximum amount of records returned" + ) + ) .inputValueDefinition(input(ProjectionBase.SKIP, TypeInt, "Defines the amount of records to be skipped")) .build() typeDefinitionRegistry.add(optionsTypeBuilder.build()) @@ -213,7 +251,7 @@ abstract class AugmentationHandler( val sortType = typeDefinitionRegistry.getType(sortTypeName)?.unwrap() if (sortType != null) { return (sortType as? InputObjectTypeDefinition)?.name - ?: throw IllegalStateException("Ordering type $type.name is already defined but not an input type") + ?: throw IllegalStateException("Ordering type $type.name is already defined but not an input type") } val relevantFields = type.getScalarFields() if (relevantFields.isEmpty()) { @@ -234,7 +272,7 @@ abstract class AugmentationHandler( var existingOrderingType = typeDefinitionRegistry.getType(orderingName)?.unwrap() if (existingOrderingType != null) { return (existingOrderingType as? EnumTypeDefinition)?.name - ?: throw IllegalStateException("Ordering type $type.name is already defined but not an input type") + ?: throw IllegalStateException("Ordering type $type.name is already defined but not an input type") } val sortingFields = type.getScalarFields() if (sortingFields.isEmpty()) { @@ -260,7 +298,7 @@ abstract class AugmentationHandler( var inputType = typeDefinitionRegistry.getType(inputName)?.unwrap() if (inputType != null) { return inputType as? InputObjectTypeDefinition - ?: throw IllegalStateException("Filter type $inputName is already defined but not an input type") + ?: throw IllegalStateException("Filter type $inputName is already defined but not an input type") } inputType = getInputType(inputName, relevantFields) typeDefinitionRegistry.add(inputType) @@ -279,15 +317,17 @@ abstract class AugmentationHandler( return neo4jTypeDefinitions .find { it.typeDefinition == type.name() } ?.let { TypeName(it.inputDefinition) } - ?: throw IllegalArgumentException("Cannot find input type for ${type.name()}") + ?: throw IllegalArgumentException("Cannot find input type for ${type.name()}") } return type } - private fun getTypeFromAnyRegistry(name: String?): TypeDefinition<*>? = typeDefinitionRegistry.getUnwrappedType(name) + private fun getTypeFromAnyRegistry(name: String?): TypeDefinition<*>? = + typeDefinitionRegistry.getUnwrappedType(name) ?: neo4jTypeDefinitionRegistry.getUnwrappedType(name) - fun ImplementingTypeDefinition<*>.relationship(): RelationshipInfo>? = RelationshipInfo.create(this, neo4jTypeDefinitionRegistry) + fun ImplementingTypeDefinition<*>.relationship(): RelationshipInfo>? = + RelationshipInfo.create(this, neo4jTypeDefinitionRegistry) fun ImplementingTypeDefinition<*>.getScalarFields(): List = fieldDefinitions .filterNot { it.isIgnored() } @@ -317,28 +357,37 @@ abstract class AugmentationHandler( fun FieldDefinition.isNativeId(): Boolean = name == ProjectionBase.NATIVE_ID fun FieldDefinition.dynamicPrefix(): String? = - getDirectiveArgument(DirectiveConstants.DYNAMIC, DirectiveConstants.DYNAMIC_PREFIX, null) + getDirectiveArgument(DirectiveConstants.DYNAMIC, DirectiveConstants.DYNAMIC_PREFIX, null) fun FieldDefinition.isRelationship(): Boolean = - !type.inner().isNeo4jType() && type.resolve() is ImplementingTypeDefinition<*> + !type.inner().isNeo4jType() && type.resolve() is ImplementingTypeDefinition<*> - fun TypeDefinitionRegistry.getUnwrappedType(name: String?): TypeDefinition>? = getType(name)?.unwrap() + fun TypeDefinitionRegistry.getUnwrappedType(name: String?): TypeDefinition>? = + getType(name)?.unwrap() fun DirectivesContainer<*>.cypherDirective(): CypherDirective? = if (hasDirective(DirectiveConstants.CYPHER)) { CypherDirective( - getMandatoryDirectiveArgument(DirectiveConstants.CYPHER, DirectiveConstants.CYPHER_STATEMENT), - getMandatoryDirectiveArgument(DirectiveConstants.CYPHER, DirectiveConstants.CYPHER_PASS_THROUGH, false) + getMandatoryDirectiveArgument(DirectiveConstants.CYPHER, DirectiveConstants.CYPHER_STATEMENT), + getMandatoryDirectiveArgument(DirectiveConstants.CYPHER, DirectiveConstants.CYPHER_PASS_THROUGH, false) ) } else { null } - fun DirectivesContainer<*>.getDirectiveArgument(directiveName: String, argumentName: String, defaultValue: T? = null): T? = - getDirectiveArgument(neo4jTypeDefinitionRegistry, directiveName, argumentName, defaultValue) - - private fun DirectivesContainer<*>.getMandatoryDirectiveArgument(directiveName: String, argumentName: String, defaultValue: T? = null): T = - getDirectiveArgument(directiveName, argumentName, defaultValue) - ?: throw IllegalStateException("No default value for @${directiveName}::$argumentName") + fun DirectivesContainer<*>.getDirectiveArgument( + directiveName: String, + argumentName: String, + defaultValue: T? = null + ): T? = + getDirectiveArgument(neo4jTypeDefinitionRegistry, directiveName, argumentName, defaultValue) + + private fun DirectivesContainer<*>.getMandatoryDirectiveArgument( + directiveName: String, + argumentName: String, + defaultValue: T? = null + ): T = + getDirectiveArgument(directiveName, argumentName, defaultValue) + ?: throw IllegalStateException("No default value for @${directiveName}::$argumentName") fun input(name: String, type: Type<*>, description: String? = null): InputValueDefinition { val input = InputValueDefinition diff --git a/core/src/main/kotlin/org/neo4j/graphql/Cypher.kt b/core/src/main/kotlin/org/neo4j/graphql/Cypher.kt index d846bff7..d33a2b19 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/Cypher.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/Cypher.kt @@ -2,6 +2,11 @@ package org.neo4j.graphql import graphql.schema.GraphQLType -data class Cypher @JvmOverloads constructor(val query: String, val params: Map = emptyMap(), var type: GraphQLType? = null, val variable: String) { +data class Cypher @JvmOverloads constructor( + val query: String, + val params: Map = emptyMap(), + var type: GraphQLType? = null, + val variable: String +) { fun with(p: Map) = this.copy(params = this.params + p) } diff --git a/core/src/main/kotlin/org/neo4j/graphql/ExtensionFunctions.kt b/core/src/main/kotlin/org/neo4j/graphql/ExtensionFunctions.kt index 83400ac5..9643258a 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/ExtensionFunctions.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/ExtensionFunctions.kt @@ -17,9 +17,11 @@ fun queryParameter(value: Any?, vararg parts: String?): Parameter<*> { } fun Expression.collect(type: GraphQLOutputType) = if (type.isList()) Cypher.collect(this) else this -fun StatementBuilder.OngoingReading.withSubQueries(subQueries: List) = subQueries.fold(this, { it, sub -> it.call(sub) }) +fun StatementBuilder.OngoingReading.withSubQueries(subQueries: List) = + subQueries.fold(this, { it, sub -> it.call(sub) }) -fun normalizeName(vararg parts: String?) = parts.mapNotNull { it?.capitalize() }.filter { it.isNotBlank() }.joinToString("").decapitalize() +fun normalizeName(vararg parts: String?) = + parts.mapNotNull { it?.capitalize() }.filter { it.isNotBlank() }.joinToString("").decapitalize() fun PropertyContainer.id(): FunctionInvocation = when (this) { is Node -> elementId(this) @@ -33,7 +35,9 @@ fun Optional.unwrap(): T? = orElse(null) fun String.asDescription() = Description(this, null, this.contains("\n")) -fun String.capitalize(): String = replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } +fun String.capitalize(): String = + replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + fun String.decapitalize(): String = replaceFirstChar { it.lowercase(Locale.getDefault()) } fun String.toUpperCase(): String = uppercase(Locale.getDefault()) fun String.toLowerCase(): String = lowercase(Locale.getDefault()) diff --git a/core/src/main/kotlin/org/neo4j/graphql/GraphQLExtensions.kt b/core/src/main/kotlin/org/neo4j/graphql/GraphQLExtensions.kt index 8f0172e4..fc53b825 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/GraphQLExtensions.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/GraphQLExtensions.kt @@ -39,7 +39,8 @@ fun GraphQLType.inner(): GraphQLType = when (this) { } fun GraphQLType.name(): String? = (this as? GraphQLNamedType)?.name -fun GraphQLType.requiredName(): String = requireNotNull(name()) { "name is required but cannot be determined for " + this.javaClass } +fun GraphQLType.requiredName(): String = + requireNotNull(name()) { "name is required but cannot be determined for " + this.javaClass } fun GraphQLType.isList() = this is GraphQLList || (this is GraphQLNonNull && this.wrappedType is GraphQLList) fun GraphQLType.isNeo4jType() = this.innerName().startsWith("_Neo4j") @@ -50,12 +51,15 @@ fun TypeDefinition<*>.isNeo4jSpatialType() = this.name.startsWith("_Neo4jPoint") fun GraphQLFieldDefinition.isNeo4jType(): Boolean = this.type.isNeo4jType() fun GraphQLFieldDefinition.isNeo4jTemporalType(): Boolean = this.type.isNeo4jTemporalType() -fun GraphQLFieldDefinition.isRelationship() = !type.isNeo4jType() && this.type.inner().let { it is GraphQLFieldsContainer } +fun GraphQLFieldDefinition.isRelationship() = + !type.isNeo4jType() && this.type.inner().let { it is GraphQLFieldsContainer } + +fun GraphQLFieldsContainer.isRelationType() = + (this as? GraphQLDirectiveContainer)?.getAppliedDirective(DirectiveConstants.RELATION) != null -fun GraphQLFieldsContainer.isRelationType() = (this as? GraphQLDirectiveContainer)?.getAppliedDirective(DirectiveConstants.RELATION) != null fun GraphQLFieldsContainer.relationshipFor(name: String): RelationshipInfo? { val field = getRelevantFieldDefinition(name) - ?: throw IllegalArgumentException("$name is not defined on ${this.name}") + ?: throw IllegalArgumentException("$name is not defined on ${this.name}") val fieldObjectType = field.type.inner() as? GraphQLImplementingType ?: return null val (relDirective, inverse) = if (isRelationType()) { @@ -63,19 +67,28 @@ fun GraphQLFieldsContainer.relationshipFor(name: String): RelationshipInfo { @@ -94,7 +107,7 @@ fun GraphQLFieldsContainer.label(): String = when { (this as? GraphQLDirectiveContainer) ?.getAppliedDirective(DirectiveConstants.RELATION) ?.getArgument(RELATION_NAME)?.argumentValue?.value?.toJavaValue()?.toString() - ?: this.name + ?: this.name else -> name } @@ -116,36 +129,51 @@ fun SelectedField.contextualize(variable: String) = variable + this.aliasOrName( fun SelectedField.contextualize(variable: SymbolicName) = variable.value + this.aliasOrName().capitalize() fun GraphQLType.innerName(): String = inner().name() - ?: throw IllegalStateException("inner name cannot be retrieved for " + this.javaClass) + ?: throw IllegalStateException("inner name cannot be retrieved for " + this.javaClass) fun GraphQLFieldDefinition.propertyName() = getDirectiveArgument(PROPERTY, PROPERTY_NAME, this.name)!! fun GraphQLFieldDefinition.dynamicPrefix(): String? = getDirectiveArgument(DYNAMIC, DYNAMIC_PREFIX, null as String?) fun GraphQLType.getInnerFieldsContainer() = inner() as? GraphQLFieldsContainer - ?: throw IllegalArgumentException("${this.innerName()} is neither an object nor an interface") + ?: throw IllegalArgumentException("${this.innerName()} is neither an object nor an interface") -fun GraphQLDirectiveContainer.getDirectiveArgument(directiveName: String, argumentName: String, defaultValue: T?): T? = - getAppliedDirective(directiveName)?.getArgument(argumentName, defaultValue) ?: defaultValue +fun GraphQLDirectiveContainer.getDirectiveArgument( + directiveName: String, + argumentName: String, + defaultValue: T? +): T? = + getAppliedDirective(directiveName)?.getArgument(argumentName, defaultValue) ?: defaultValue @Suppress("UNCHECKED_CAST") -fun DirectivesContainer<*>.getDirectiveArgument(typeRegistry: TypeDefinitionRegistry, directiveName: String, argumentName: String, defaultValue: T? = null): T? { +fun DirectivesContainer<*>.getDirectiveArgument( + typeRegistry: TypeDefinitionRegistry, + directiveName: String, + argumentName: String, + defaultValue: T? = null +): T? { return (getDirective(directiveName) ?: return defaultValue) .getArgument(argumentName)?.value?.toJavaValue() as T? - ?: typeRegistry.getDirectiveDefinition(directiveName) - ?.unwrap() - ?.inputValueDefinitions - ?.find { inputValueDefinition -> inputValueDefinition.name == argumentName } - ?.defaultValue?.toJavaValue() as T? - ?: defaultValue + ?: typeRegistry.getDirectiveDefinition(directiveName) + ?.unwrap() + ?.inputValueDefinitions + ?.find { inputValueDefinition -> inputValueDefinition.name == argumentName } + ?.defaultValue?.toJavaValue() as T? + ?: defaultValue } fun DirectivesContainer<*>.getDirective(name: String): Directive? = directives.firstOrNull { it.name == name } -fun DirectivesContainer<*>.getMandatoryDirectiveArgument(typeRegistry: TypeDefinitionRegistry, directiveName: String, argumentName: String, defaultValue: T? = null): T = - getDirectiveArgument(typeRegistry, directiveName, argumentName, defaultValue) - ?: throw IllegalStateException("No default value for @${directiveName}::$argumentName") - -fun GraphQLAppliedDirective.getMandatoryArgument(argumentName: String, defaultValue: T? = null): T = this.getArgument(argumentName, defaultValue) +fun DirectivesContainer<*>.getMandatoryDirectiveArgument( + typeRegistry: TypeDefinitionRegistry, + directiveName: String, + argumentName: String, + defaultValue: T? = null +): T = + getDirectiveArgument(typeRegistry, directiveName, argumentName, defaultValue) + ?: throw IllegalStateException("No default value for @${directiveName}::$argumentName") + +fun GraphQLAppliedDirective.getMandatoryArgument(argumentName: String, defaultValue: T? = null): T = + this.getArgument(argumentName, defaultValue) ?: throw IllegalStateException(argumentName + " is required for @${this.name}") fun GraphQLAppliedDirective.getArgument(argumentName: String, defaultValue: T? = null): T? { @@ -165,8 +193,10 @@ fun GraphQLFieldDefinition.cypherDirective(): CypherDirective? = getAppliedDirec val rewrittenStatement = originalStatement.replace(Regex("\\\$([_a-zA-Z]\\w*)"), "$1") if (originalStatement != rewrittenStatement) { LoggerFactory.getLogger(CypherDirective::class.java) - .warn("The field arguments used in the directives statement must not contain parameters. The statement was replaced. Please adjust your GraphQl Schema.\n\tGot : {}\n\tReplaced by: {}\n\tField : {} ({})", - originalStatement, rewrittenStatement, this.name, this.definition?.sourceLocation) + .warn( + "The field arguments used in the directives statement must not contain parameters. The statement was replaced. Please adjust your GraphQl Schema.\n\tGot : {}\n\tReplaced by: {}\n\tField : {} ({})", + originalStatement, rewrittenStatement, this.name, this.definition?.sourceLocation + ) } CypherDirective(rewrittenStatement, it.getMandatoryArgument(CYPHER_PASS_THROUGH, false)) } @@ -206,10 +236,16 @@ fun GraphQLFieldsContainer.getRelevantFieldDefinitions() = this.fieldDefinitions /** * Returns the field definition if it is not ignored */ -fun GraphQLFieldsContainer.getRelevantFieldDefinition(name: String?) = this.getFieldDefinition(name)?.takeIf { !it.isIgnored() } +fun GraphQLFieldsContainer.getRelevantFieldDefinition(name: String?) = + this.getFieldDefinition(name)?.takeIf { !it.isIgnored() } -fun InputObjectTypeDefinition.Builder.addFilterField(fieldName: String, isList: Boolean, filterType: String, description: Description? = null) { +fun InputObjectTypeDefinition.Builder.addFilterField( + fieldName: String, + isList: Boolean, + filterType: String, + description: Description? = null +) { val wrappedType: Type<*> = when { isList -> ListType(TypeName(filterType)) else -> TypeName(filterType) @@ -224,7 +260,11 @@ fun InputObjectTypeDefinition.Builder.addFilterField(fieldName: String, isList: this.inputValueDefinition(inputField.build()) } -fun InputObjectTypeDefinition.Builder.addArrayFilterField(fieldName: String, filterType: String, description: Description? = null) { +fun InputObjectTypeDefinition.Builder.addArrayFilterField( + fieldName: String, + filterType: String, + description: Description? = null +) { val inputField = InputValueDefinition.newInputValueDefinition() .name(fieldName) .type(ListType(NonNullType(TypeName(filterType)))) @@ -238,10 +278,11 @@ fun InputObjectTypeDefinition.Builder.addArrayFilterField(fieldName: String, fil fun TypeDefinitionRegistry.queryTypeName() = this.getOperationType("query") ?: "Query" fun TypeDefinitionRegistry.mutationTypeName() = this.getOperationType("mutation") ?: "Mutation" fun TypeDefinitionRegistry.subscriptionTypeName() = this.getOperationType("subscription") ?: "Subscription" -fun TypeDefinitionRegistry.getOperationType(name: String) = this.schemaDefinition().unwrap()?.operationTypeDefinitions?.firstOrNull { it.name == name }?.typeName?.name +fun TypeDefinitionRegistry.getOperationType(name: String) = + this.schemaDefinition().unwrap()?.operationTypeDefinitions?.firstOrNull { it.name == name }?.typeName?.name fun DataFetchingEnvironment.typeAsContainer() = this.fieldDefinition.type.inner() as? GraphQLFieldsContainer - ?: throw IllegalStateException("expect type of field ${this.logField()} to be GraphQLFieldsContainer, but was ${this.fieldDefinition.type.name()}") + ?: throw IllegalStateException("expect type of field ${this.logField()} to be GraphQLFieldsContainer, but was ${this.fieldDefinition.type.name()}") fun DataFetchingEnvironment.logField() = "${this.parentType.name()}.${this.fieldDefinition.name}" fun DataFetchingEnvironment.queryContext(): QueryContext = this.graphQlContext.get(QueryContext.KEY) ?: QueryContext() diff --git a/core/src/main/kotlin/org/neo4j/graphql/InvalidQueryException.kt b/core/src/main/kotlin/org/neo4j/graphql/InvalidQueryException.kt index d1daedf1..fd75b1a8 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/InvalidQueryException.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/InvalidQueryException.kt @@ -2,4 +2,5 @@ package org.neo4j.graphql import graphql.GraphQLError -class InvalidQueryException(@Suppress("MemberVisibilityCanBePrivate") val error: GraphQLError) : RuntimeException(error.message) +class InvalidQueryException(@Suppress("MemberVisibilityCanBePrivate") val error: GraphQLError) : + RuntimeException(error.message) diff --git a/core/src/main/kotlin/org/neo4j/graphql/Neo4jTypes.kt b/core/src/main/kotlin/org/neo4j/graphql/Neo4jTypes.kt index a4e0419c..a2a01d1d 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/Neo4jTypes.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/Neo4jTypes.kt @@ -11,9 +11,9 @@ const val NEO4j_POINT_DISTANCE_FILTER = "_Neo4jPointDistanceFilter" const val NEO4j_POINT_DISTANCE_FILTER_SUFFIX = "_distance" data class TypeDefinition( - val name: String, - val typeDefinition: String, - val inputDefinition: String = typeDefinition + "Input" + val name: String, + val typeDefinition: String, + val inputDefinition: String = typeDefinition + "Input" ) class Neo4jTemporalConverter(name: String) : Neo4jSimpleConverter(name) { @@ -21,7 +21,11 @@ class Neo4jTemporalConverter(name: String) : Neo4jSimpleConverter(name) { return Cypher.call("toString").withArgs(variable.property(field.name)).asFunction() } - override fun createCondition(property: Property, parameter: Parameter<*>, conditionCreator: (Expression, Expression) -> Condition): Condition { + override fun createCondition( + property: Property, + parameter: Parameter<*>, + conditionCreator: (Expression, Expression) -> Condition + ): Condition { return conditionCreator(property, toExpression(parameter)) } } @@ -29,11 +33,11 @@ class Neo4jTemporalConverter(name: String) : Neo4jSimpleConverter(name) { class Neo4jTimeConverter(name: String) : Neo4jConverter(name) { override fun createCondition( - fieldName: String, - field: GraphQLFieldDefinition, - parameter: Parameter<*>, - conditionCreator: (Expression, Expression) -> Condition, - propertyContainer: PropertyContainer + fieldName: String, + field: GraphQLFieldDefinition, + parameter: Parameter<*>, + conditionCreator: (Expression, Expression) -> Condition, + propertyContainer: PropertyContainer ): Condition = if (fieldName == NEO4j_FORMATTED_PROPERTY_KEY) { val exp = toExpression(parameter) conditionCreator(propertyContainer.property(field.name), exp) @@ -46,7 +50,10 @@ class Neo4jTimeConverter(name: String) : Neo4jConverter(name) { else -> super.projectField(variable, field, name) } - override fun getMutationExpression(value: Any, field: GraphQLFieldDefinition): BaseDataFetcherForContainer.PropertyAccessor { + override fun getMutationExpression( + value: Any, + field: GraphQLFieldDefinition + ): BaseDataFetcherForContainer.PropertyAccessor { val fieldName = field.name return (value as? Map<*, *>) ?.get(NEO4j_FORMATTED_PROPERTY_KEY) @@ -56,13 +63,17 @@ class Neo4jTimeConverter(name: String) : Neo4jConverter(name) { toExpression(param.property(NEO4j_FORMATTED_PROPERTY_KEY)) } } - ?: super.getMutationExpression(value, field) + ?: super.getMutationExpression(value, field) } } class Neo4jPointConverter(name: String) : Neo4jConverter(name) { - fun createDistanceCondition(lhs: Expression, rhs: Parameter<*>, conditionCreator: (Expression, Expression) -> Condition): Condition { + fun createDistanceCondition( + lhs: Expression, + rhs: Parameter<*>, + conditionCreator: (Expression, Expression) -> Condition + ): Condition { val point = Cypher.point(rhs.property("point")) val distance = rhs.property("distance") return conditionCreator(Cypher.distance(lhs, point), distance) @@ -70,9 +81,9 @@ class Neo4jPointConverter(name: String) : Neo4jConverter(name) { } open class Neo4jConverter( - name: String, - val prefixedName: String = "_Neo4j$name", - val typeDefinition: TypeDefinition = TypeDefinition(name, prefixedName) + name: String, + val prefixedName: String = "_Neo4j$name", + val typeDefinition: TypeDefinition = TypeDefinition(name, prefixedName) ) : Neo4jSimpleConverter(name) open class Neo4jSimpleConverter(val name: String) { @@ -81,47 +92,52 @@ open class Neo4jSimpleConverter(val name: String) { } open fun createCondition( - property: Property, - parameter: Parameter<*>, - conditionCreator: (Expression, Expression) -> Condition + property: Property, + parameter: Parameter<*>, + conditionCreator: (Expression, Expression) -> Condition ): Condition = conditionCreator(property, parameter) open fun createCondition( - fieldName: String, - field: GraphQLFieldDefinition, - parameter: Parameter<*>, - conditionCreator: (Expression, Expression) -> Condition, - propertyContainer: PropertyContainer + fieldName: String, + field: GraphQLFieldDefinition, + parameter: Parameter<*>, + conditionCreator: (Expression, Expression) -> Condition, + propertyContainer: PropertyContainer ): Condition = createCondition(propertyContainer.property(field.name, fieldName), parameter, conditionCreator) - open fun projectField(variable: SymbolicName, field: SelectedField, name: String): Any = variable.property(field.name, name) + open fun projectField(variable: SymbolicName, field: SelectedField, name: String): Any = + variable.property(field.name, name) - open fun getMutationExpression(value: Any, field: GraphQLFieldDefinition): BaseDataFetcherForContainer.PropertyAccessor { + open fun getMutationExpression( + value: Any, + field: GraphQLFieldDefinition + ): BaseDataFetcherForContainer.PropertyAccessor { return BaseDataFetcherForContainer.PropertyAccessor(field.name) { variable -> toExpression(queryParameter(value, variable, field.name)) } } } -fun getNeo4jTypeConverter(field: GraphQLFieldDefinition): Neo4jSimpleConverter = getNeo4jTypeConverter(field.type.innerName()) +fun getNeo4jTypeConverter(field: GraphQLFieldDefinition): Neo4jSimpleConverter = + getNeo4jTypeConverter(field.type.innerName()) private fun getNeo4jTypeConverter(name: String): Neo4jSimpleConverter = - neo4jConverter[name] ?: neo4jScalarConverter[name] ?: throw RuntimeException("Type $name not found") + neo4jConverter[name] ?: neo4jScalarConverter[name] ?: throw RuntimeException("Type $name not found") private val neo4jConverter = listOf( - Neo4jTimeConverter("LocalTime"), - Neo4jTimeConverter("Date"), - Neo4jTimeConverter("DateTime"), - Neo4jTimeConverter("Time"), - Neo4jTimeConverter("LocalDateTime"), - Neo4jPointConverter("Point"), + Neo4jTimeConverter("LocalTime"), + Neo4jTimeConverter("Date"), + Neo4jTimeConverter("DateTime"), + Neo4jTimeConverter("Time"), + Neo4jTimeConverter("LocalDateTime"), + Neo4jPointConverter("Point"), ).associateBy { it.prefixedName } private val neo4jScalarConverter = listOf( - Neo4jTemporalConverter("LocalTime"), - Neo4jTemporalConverter("Date"), - Neo4jTemporalConverter("DateTime"), - Neo4jTemporalConverter("Time"), - Neo4jTemporalConverter("LocalDateTime") + Neo4jTemporalConverter("LocalTime"), + Neo4jTemporalConverter("Date"), + Neo4jTemporalConverter("DateTime"), + Neo4jTemporalConverter("Time"), + Neo4jTemporalConverter("LocalDateTime") ).associateBy { it.name } val NEO4j_TEMPORAL_TYPES = neo4jScalarConverter.keys diff --git a/core/src/main/kotlin/org/neo4j/graphql/NoOpCoercing.kt b/core/src/main/kotlin/org/neo4j/graphql/NoOpCoercing.kt index 50704969..a6854a0e 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/NoOpCoercing.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/NoOpCoercing.kt @@ -7,10 +7,10 @@ import graphql.schema.CoercingParseValueException object NoOpCoercing : Coercing { override fun parseLiteral(input: Any) = input.toJavaValue() - ?: throw CoercingParseLiteralException("literal should not be null") + ?: throw CoercingParseLiteralException("literal should not be null") override fun serialize(dataFetcherResult: Any) = dataFetcherResult override fun parseValue(input: Any) = input.toJavaValue() - ?: throw CoercingParseValueException("literal should not be null") + ?: throw CoercingParseValueException("literal should not be null") } diff --git a/core/src/main/kotlin/org/neo4j/graphql/Predicates.kt b/core/src/main/kotlin/org/neo4j/graphql/Predicates.kt index 7e858146..ee3afa35 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/Predicates.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/Predicates.kt @@ -8,18 +8,19 @@ import org.neo4j.cypherdsl.core.* import org.neo4j.cypherdsl.core.Cypher import org.slf4j.LoggerFactory -private fun createArrayPredicate(factory: (SymbolicName) -> Predicates.OngoingListBasedPredicateFunction) = { lhs: Expression, rhs: Expression -> - val x: SymbolicName = Cypher.name("x") - factory(x).`in`(lhs).where(x.`in`(rhs)) -} +private fun createArrayPredicate(factory: (SymbolicName) -> Predicates.OngoingListBasedPredicateFunction) = + { lhs: Expression, rhs: Expression -> + val x: SymbolicName = Cypher.name("x") + factory(x).`in`(lhs).where(x.`in`(rhs)) + } enum class FieldOperator( - val suffix: String, - private val conditionCreator: (Expression, Expression) -> Condition, - val not: Boolean = false, - val requireParam: Boolean = true, - val distance: Boolean = false, - val list: Boolean = false + val suffix: String, + private val conditionCreator: (Expression, Expression) -> Condition, + val not: Boolean = false, + val requireParam: Boolean = true, + val distance: Boolean = false, + val list: Boolean = false ) { EQ("", { lhs, rhs -> lhs.isEqualTo(rhs) }), IS_NULL("", { lhs, _ -> lhs.isNull }, requireParam = false), @@ -52,19 +53,25 @@ enum class FieldOperator( DISTANCE_GTE(NEO4j_POINT_DISTANCE_FILTER_SUFFIX + "_gte", { lhs, rhs -> lhs.gte(rhs) }, distance = true); fun resolveCondition( - variablePrefix: String, - queriedField: String, - propertyContainer: PropertyContainer, - field: GraphQLFieldDefinition?, - value: Any?, - schemaConfig: SchemaConfig, - suffix: String? = null + variablePrefix: String, + queriedField: String, + propertyContainer: PropertyContainer, + field: GraphQLFieldDefinition?, + value: Any?, + schemaConfig: SchemaConfig, + suffix: String? = null ): List { if (schemaConfig.useTemporalScalars && field?.type?.isNeo4jTemporalType() == true) { val neo4jTypeConverter = getNeo4jTypeConverter(field) val parameter = queryParameter(value, variablePrefix, queriedField, null, suffix) .withValue(value) - return listOf(neo4jTypeConverter.createCondition(propertyContainer.property(field.name), parameter, conditionCreator)) + return listOf( + neo4jTypeConverter.createCondition( + propertyContainer.property(field.name), + parameter, + conditionCreator + ) + ) } return if (field?.type?.isNeo4jType() == true && value is Map<*, *>) { resolveNeo4jTypeConditions(variablePrefix, queriedField, propertyContainer, field, value, suffix) @@ -79,34 +86,64 @@ enum class FieldOperator( } listOf(condition) } else { - resolveCondition(variablePrefix, queriedField, propertyContainer.property(field?.propertyName() - ?: queriedField), value, suffix) + resolveCondition( + variablePrefix, queriedField, propertyContainer.property( + field?.propertyName() + ?: queriedField + ), value, suffix + ) } } - private fun resolveNeo4jTypeConditions(variablePrefix: String, queriedField: String, propertyContainer: PropertyContainer, field: GraphQLFieldDefinition, values: Map<*, *>, suffix: String?): List { + private fun resolveNeo4jTypeConditions( + variablePrefix: String, + queriedField: String, + propertyContainer: PropertyContainer, + field: GraphQLFieldDefinition, + values: Map<*, *>, + suffix: String? + ): List { val neo4jTypeConverter = getNeo4jTypeConverter(field) val conditions = mutableListOf() if (distance) { val parameter = queryParameter(values, variablePrefix, queriedField, suffix) conditions += (neo4jTypeConverter as Neo4jPointConverter).createDistanceCondition( - propertyContainer.property(field.propertyName()), - parameter, - conditionCreator + propertyContainer.property(field.propertyName()), + parameter, + conditionCreator ) } else { values.entries.forEachIndexed { index, (key, value) -> val fieldName = key.toString() - val parameter = queryParameter(value, variablePrefix, queriedField, if (values.size > 1) "And${index + 1}" else null, suffix, fieldName) + val parameter = queryParameter( + value, + variablePrefix, + queriedField, + if (values.size > 1) "And${index + 1}" else null, + suffix, + fieldName + ) .withValue(value) - conditions += neo4jTypeConverter.createCondition(fieldName, field, parameter, conditionCreator, propertyContainer) + conditions += neo4jTypeConverter.createCondition( + fieldName, + field, + parameter, + conditionCreator, + propertyContainer + ) } } return conditions } - private fun resolveCondition(variablePrefix: String, queriedField: String, property: Property, value: Any?, suffix: String?): List { + private fun resolveCondition( + variablePrefix: String, + queriedField: String, + property: Property, + value: Any?, + suffix: String? + ): List { val parameter = queryParameter(value, variablePrefix, queriedField, suffix) val condition = conditionCreator(property, parameter) return listOf(condition) @@ -115,19 +152,27 @@ enum class FieldOperator( companion object { fun forType(type: TypeDefinition<*>, isNeo4jType: Boolean, isList: Boolean): List = - when { - isList -> listOf(EQ, NEQ, INCLUDES_ALL, INCLUDES_NONE, INCLUDES_SOME, INCLUDES_SINGLE) - type.name == TypeBoolean.name -> listOf(EQ, NEQ) - type.name == NEO4j_POINT_DISTANCE_FILTER -> listOf(EQ, LT, LTE, GT, GTE) - type.isNeo4jSpatialType() -> listOf(EQ, NEQ) - isNeo4jType -> listOf(EQ, NEQ, IN, NIN) - type is ImplementingTypeDefinition<*> -> throw IllegalArgumentException("This operators are not for relations, use the RelationOperator instead") - type is EnumTypeDefinition -> listOf(EQ, NEQ, IN, NIN) - // todo list types - type !is ScalarTypeDefinition -> listOf(EQ, NEQ, IN, NIN) - else -> listOf(EQ, NEQ, IN, NIN, LT, LTE, GT, GTE) + - if (type.name == "String" || type.name == "ID") listOf(C, NC, SW, NSW, EW, NEW, MATCHES) else emptyList() - } + when { + isList -> listOf(EQ, NEQ, INCLUDES_ALL, INCLUDES_NONE, INCLUDES_SOME, INCLUDES_SINGLE) + type.name == TypeBoolean.name -> listOf(EQ, NEQ) + type.name == NEO4j_POINT_DISTANCE_FILTER -> listOf(EQ, LT, LTE, GT, GTE) + type.isNeo4jSpatialType() -> listOf(EQ, NEQ) + isNeo4jType -> listOf(EQ, NEQ, IN, NIN) + type is ImplementingTypeDefinition<*> -> throw IllegalArgumentException("This operators are not for relations, use the RelationOperator instead") + type is EnumTypeDefinition -> listOf(EQ, NEQ, IN, NIN) + // todo list types + type !is ScalarTypeDefinition -> listOf(EQ, NEQ, IN, NIN) + else -> listOf(EQ, NEQ, IN, NIN, LT, LTE, GT, GTE) + + if (type.name == "String" || type.name == "ID") listOf( + C, + NC, + SW, + NSW, + EW, + NEW, + MATCHES + ) else emptyList() + } } fun fieldName(fieldName: String) = fieldName + suffix @@ -147,67 +192,99 @@ enum class RelationOperator(val suffix: String) { fun fieldName(fieldName: String) = fieldName + suffix - fun harmonize(type: GraphQLFieldsContainer, field: GraphQLFieldDefinition, value: Any?, queryFieldName: String) = when (field.type.isList()) { - true -> when (this) { - NOT -> when (value) { - null -> NOT - else -> NONE - } - EQ_OR_NOT_EXISTS -> when (value) { - null -> EQ_OR_NOT_EXISTS - else -> { - LOGGER.debug("$queryFieldName on type ${type.name} was used for filtering, consider using ${field.name}${EVERY.suffix} instead") - EVERY + fun harmonize(type: GraphQLFieldsContainer, field: GraphQLFieldDefinition, value: Any?, queryFieldName: String) = + when (field.type.isList()) { + true -> when (this) { + NOT -> when (value) { + null -> NOT + else -> NONE } + + EQ_OR_NOT_EXISTS -> when (value) { + null -> EQ_OR_NOT_EXISTS + else -> { + LOGGER.debug("$queryFieldName on type ${type.name} was used for filtering, consider using ${field.name}${EVERY.suffix} instead") + EVERY + } + } + + else -> this } - else -> this - } - false -> when (this) { - SINGLE -> { - LOGGER.debug("Using $queryFieldName on type ${type.name} is deprecated, use ${field.name} directly") - SOME - } - SOME -> { - LOGGER.debug("Using $queryFieldName on type ${type.name} is deprecated, use ${field.name} directly") - SOME - } - NONE -> { - LOGGER.debug("Using $queryFieldName on type ${type.name} is deprecated, use ${field.name}${NOT.suffix} instead") - NONE - } - NOT -> when (value) { - null -> NOT - else -> NONE - } - EQ_OR_NOT_EXISTS -> when (value) { - null -> EQ_OR_NOT_EXISTS - else -> SOME + + false -> when (this) { + SINGLE -> { + LOGGER.debug("Using $queryFieldName on type ${type.name} is deprecated, use ${field.name} directly") + SOME + } + + SOME -> { + LOGGER.debug("Using $queryFieldName on type ${type.name} is deprecated, use ${field.name} directly") + SOME + } + + NONE -> { + LOGGER.debug("Using $queryFieldName on type ${type.name} is deprecated, use ${field.name}${NOT.suffix} instead") + NONE + } + + NOT -> when (value) { + null -> NOT + else -> NONE + } + + EQ_OR_NOT_EXISTS -> when (value) { + null -> EQ_OR_NOT_EXISTS + else -> SOME + } + + else -> this } - else -> this } - } companion object { private val LOGGER = LoggerFactory.getLogger(RelationOperator::class.java) - fun createRelationFilterFields(type: TypeDefinition<*>, field: FieldDefinition, filterType: String, builder: InputObjectTypeDefinition.Builder) { + fun createRelationFilterFields( + type: TypeDefinition<*>, + field: FieldDefinition, + filterType: String, + builder: InputObjectTypeDefinition.Builder + ) { val list = field.type.isList() val addFilterField = { op: RelationOperator, description: String -> builder.addFilterField(op.fieldName(field.name), false, filterType, description.asDescription()) } - addFilterField(EQ_OR_NOT_EXISTS, "Filters only those `${type.name}` for which ${if (list) "all" else "the"} `${field.name}`-relationship matches this filter. " + - "If `null` is passed to this field, only those `${type.name}` will be filtered which has no `${field.name}`-relations") + addFilterField( + EQ_OR_NOT_EXISTS, + "Filters only those `${type.name}` for which ${if (list) "all" else "the"} `${field.name}`-relationship matches this filter. " + + "If `null` is passed to this field, only those `${type.name}` will be filtered which has no `${field.name}`-relations" + ) - addFilterField(NOT, "Filters only those `${type.name}` for which ${if (list) "all" else "the"} `${field.name}`-relationship does not match this filter. " + - "If `null` is passed to this field, only those `${type.name}` will be filtered which has any `${field.name}`-relation") + addFilterField( + NOT, + "Filters only those `${type.name}` for which ${if (list) "all" else "the"} `${field.name}`-relationship does not match this filter. " + + "If `null` is passed to this field, only those `${type.name}` will be filtered which has any `${field.name}`-relation" + ) if (list) { // n..m - addFilterField(EVERY, "Filters only those `${type.name}` for which all `${field.name}`-relationships matches this filter") - addFilterField(SOME, "Filters only those `${type.name}` for which at least one `${field.name}`-relationship matches this filter") - addFilterField(SINGLE, "Filters only those `${type.name}` for which exactly one `${field.name}`-relationship matches this filter") - addFilterField(NONE, "Filters only those `${type.name}` for which none of the `${field.name}`-relationships matches this filter") + addFilterField( + EVERY, + "Filters only those `${type.name}` for which all `${field.name}`-relationships matches this filter" + ) + addFilterField( + SOME, + "Filters only those `${type.name}` for which at least one `${field.name}`-relationship matches this filter" + ) + addFilterField( + SINGLE, + "Filters only those `${type.name}` for which exactly one `${field.name}`-relationship matches this filter" + ) + addFilterField( + NONE, + "Filters only those `${type.name}` for which none of the `${field.name}`-relationships matches this filter" + ) } else { // n..1 addFilterField(SINGLE, "@deprecated Use the `${field.name}`-field directly (without any suffix)") diff --git a/core/src/main/kotlin/org/neo4j/graphql/QueryContext.kt b/core/src/main/kotlin/org/neo4j/graphql/QueryContext.kt index 0f85e1bd..5efda4b4 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/QueryContext.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/QueryContext.kt @@ -3,17 +3,17 @@ package org.neo4j.graphql import org.neo4j.cypherdsl.core.renderer.Dialect data class QueryContext @JvmOverloads constructor( - /** - * if true the __typename will always be returned for interfaces, no matter if it was queried or not - */ - var queryTypeOfInterfaces: Boolean = false, + /** + * if true the __typename will always be returned for interfaces, no matter if it was queried or not + */ + var queryTypeOfInterfaces: Boolean = false, - /** - * If set alternative approaches for query translation will be used - */ - var optimizedQuery: Set? = null, + /** + * If set alternative approaches for query translation will be used + */ + var optimizedQuery: Set? = null, - var neo4jDialect: Dialect = Dialect.NEO4J_5 + var neo4jDialect: Dialect = Dialect.NEO4J_5 ) { enum class OptimizationStrategy { diff --git a/core/src/main/kotlin/org/neo4j/graphql/RelationshipInfo.kt b/core/src/main/kotlin/org/neo4j/graphql/RelationshipInfo.kt index 4ff576c7..2053b3c8 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/RelationshipInfo.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/RelationshipInfo.kt @@ -10,12 +10,12 @@ import org.neo4j.cypherdsl.core.Relationship import org.neo4j.cypherdsl.core.SymbolicName data class RelationshipInfo( - val type: TYPE, - val typeName: String, - val relType: String, - val direction: RelationDirection, - val startField: String, - val endField: String + val type: TYPE, + val typeName: String, + val relType: String, + val direction: RelationDirection, + val startField: String, + val endField: String ) { enum class RelationDirection { @@ -32,34 +32,57 @@ data class RelationshipInfo( } companion object { - fun create(type: GraphQLFieldsContainer): RelationshipInfo? = (type as? GraphQLDirectiveContainer) - ?.getAppliedDirective(DirectiveConstants.RELATION) - ?.let { relDirective -> create(type, relDirective) } + fun create(type: GraphQLFieldsContainer): RelationshipInfo? = + (type as? GraphQLDirectiveContainer) + ?.getAppliedDirective(DirectiveConstants.RELATION) + ?.let { relDirective -> create(type, relDirective) } - fun create(type: GraphQLFieldsContainer, relDirective: GraphQLAppliedDirective): RelationshipInfo { + fun create( + type: GraphQLFieldsContainer, + relDirective: GraphQLAppliedDirective + ): RelationshipInfo { val relType = relDirective.getArgument(DirectiveConstants.RELATION_NAME, "")!! val direction = relDirective.getArgument(DirectiveConstants.RELATION_DIRECTION, null) ?.let { RelationDirection.valueOf(it) } - ?: RelationDirection.OUT + ?: RelationDirection.OUT return RelationshipInfo( - type, - type.name, - relType, - direction, - relDirective.getMandatoryArgument(DirectiveConstants.RELATION_FROM), - relDirective.getMandatoryArgument(DirectiveConstants.RELATION_TO) + type, + type.name, + relType, + direction, + relDirective.getMandatoryArgument(DirectiveConstants.RELATION_FROM), + relDirective.getMandatoryArgument(DirectiveConstants.RELATION_TO) ) } - fun create(type: ImplementingTypeDefinition<*>, registry: TypeDefinitionRegistry): RelationshipInfo>? { - val relType = type.getDirectiveArgument(registry, DirectiveConstants.RELATION, DirectiveConstants.RELATION_NAME) - ?: return null - val startField = type.getMandatoryDirectiveArgument(registry, DirectiveConstants.RELATION, DirectiveConstants.RELATION_FROM) - val endField = type.getMandatoryDirectiveArgument(registry, DirectiveConstants.RELATION, DirectiveConstants.RELATION_TO) - val direction = type.getDirectiveArgument(registry, DirectiveConstants.RELATION, DirectiveConstants.RELATION_DIRECTION) + fun create( + type: ImplementingTypeDefinition<*>, + registry: TypeDefinitionRegistry + ): RelationshipInfo>? { + val relType = type.getDirectiveArgument( + registry, + DirectiveConstants.RELATION, + DirectiveConstants.RELATION_NAME + ) + ?: return null + val startField = type.getMandatoryDirectiveArgument( + registry, + DirectiveConstants.RELATION, + DirectiveConstants.RELATION_FROM + ) + val endField = type.getMandatoryDirectiveArgument( + registry, + DirectiveConstants.RELATION, + DirectiveConstants.RELATION_TO + ) + val direction = type.getDirectiveArgument( + registry, + DirectiveConstants.RELATION, + DirectiveConstants.RELATION_DIRECTION + ) ?.let { RelationDirection.valueOf(it) } - ?: RelationDirection.OUT + ?: RelationDirection.OUT return RelationshipInfo(type, type.name, relType, direction, startField, endField) } } diff --git a/core/src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt b/core/src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt index 8066089e..e88ec941 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt @@ -28,8 +28,8 @@ import org.neo4j.graphql.handler.relation.DeleteRelationHandler * Each of these steps can be called manually to enhance an existing [TypeDefinitionRegistry] */ class SchemaBuilder( - val typeDefinitionRegistry: TypeDefinitionRegistry, - val schemaConfig: SchemaConfig = SchemaConfig() + val typeDefinitionRegistry: TypeDefinitionRegistry, + val schemaConfig: SchemaConfig = SchemaConfig() ) { companion object { @@ -41,7 +41,11 @@ class SchemaBuilder( */ @JvmStatic @JvmOverloads - fun buildSchema(sdl: String, config: SchemaConfig = SchemaConfig(), dataFetchingInterceptor: DataFetchingInterceptor? = null): GraphQLSchema { + fun buildSchema( + sdl: String, + config: SchemaConfig = SchemaConfig(), + dataFetchingInterceptor: DataFetchingInterceptor? = null + ): GraphQLSchema { val schemaParser = SchemaParser() val typeDefinitionRegistry = schemaParser.parse(sdl) return buildSchema(typeDefinitionRegistry, config, dataFetchingInterceptor) @@ -55,7 +59,11 @@ class SchemaBuilder( */ @JvmStatic @JvmOverloads - fun buildSchema(typeDefinitionRegistry: TypeDefinitionRegistry, config: SchemaConfig = SchemaConfig(), dataFetchingInterceptor: DataFetchingInterceptor? = null): GraphQLSchema { + fun buildSchema( + typeDefinitionRegistry: TypeDefinitionRegistry, + config: SchemaConfig = SchemaConfig(), + dataFetchingInterceptor: DataFetchingInterceptor? = null + ): GraphQLSchema { val builder = RuntimeWiring.newRuntimeWiring() val codeRegistryBuilder = GraphQLCodeRegistry.newCodeRegistry() @@ -66,8 +74,8 @@ class SchemaBuilder( schemaBuilder.registerDataFetcher(codeRegistryBuilder, dataFetchingInterceptor) return SchemaGenerator().makeExecutableSchema( - typeDefinitionRegistry, - builder.codeRegistry(codeRegistryBuilder).build() + typeDefinitionRegistry, + builder.codeRegistry(codeRegistryBuilder).build() ) } } @@ -79,20 +87,20 @@ class SchemaBuilder( neo4jTypeDefinitionRegistry = getNeo4jEnhancements() ensureRootQueryTypeExists(typeDefinitionRegistry) handler = mutableListOf( - CypherDirectiveHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), - AugmentFieldHandler(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) + CypherDirectiveHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), + AugmentFieldHandler(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) ) if (schemaConfig.query.enabled) { handler.add(QueryHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry)) } if (schemaConfig.mutation.enabled) { handler += listOf( - MergeOrUpdateHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), - DeleteHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), - CreateTypeHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), - DeleteRelationHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), - CreateRelationTypeHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), - CreateRelationHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) + MergeOrUpdateHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), + DeleteHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), + CreateTypeHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), + DeleteRelationHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), + CreateRelationTypeHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry), + CreateRelationHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) ) } } @@ -206,22 +214,33 @@ class SchemaBuilder( */ @JvmOverloads fun registerDataFetcher( - codeRegistryBuilder: GraphQLCodeRegistry.Builder, - dataFetchingInterceptor: DataFetchingInterceptor?, - typeDefinitionRegistry: TypeDefinitionRegistry = this.typeDefinitionRegistry + codeRegistryBuilder: GraphQLCodeRegistry.Builder, + dataFetchingInterceptor: DataFetchingInterceptor?, + typeDefinitionRegistry: TypeDefinitionRegistry = this.typeDefinitionRegistry ) { if (dataFetchingInterceptor != null) { codeRegistryBuilder.defaultDataFetcher { AliasPropertyDataFetcher() } } - addDataFetcher(typeDefinitionRegistry.queryTypeName(), OperationType.QUERY, dataFetchingInterceptor, codeRegistryBuilder) - addDataFetcher(typeDefinitionRegistry.mutationTypeName(), OperationType.MUTATION, dataFetchingInterceptor, codeRegistryBuilder) + addDataFetcher( + typeDefinitionRegistry.queryTypeName(), + OperationType.QUERY, + dataFetchingInterceptor, + codeRegistryBuilder + ) + addDataFetcher( + typeDefinitionRegistry.mutationTypeName(), + OperationType.MUTATION, + dataFetchingInterceptor, + codeRegistryBuilder + ) } private fun addDataFetcher( - parentType: String, - operationType: OperationType, - dataFetchingInterceptor: DataFetchingInterceptor?, - codeRegistryBuilder: GraphQLCodeRegistry.Builder) { + parentType: String, + operationType: OperationType, + dataFetchingInterceptor: DataFetchingInterceptor?, + codeRegistryBuilder: GraphQLCodeRegistry.Builder + ) { typeDefinitionRegistry.getType(parentType)?.unwrap() ?.let { it as? ObjectTypeDefinition } ?.fieldDefinitions @@ -232,7 +251,10 @@ class SchemaBuilder( val interceptedDataFetcher: DataFetcher<*> = dataFetchingInterceptor?.let { DataFetcher { env -> dataFetchingInterceptor.fetchData(env, dataFetcher) } } ?: dataFetcher - codeRegistryBuilder.dataFetcher(FieldCoordinates.coordinates(parentType, field.name), interceptedDataFetcher) + codeRegistryBuilder.dataFetcher( + FieldCoordinates.coordinates(parentType, field.name), + interceptedDataFetcher + ) } } } @@ -254,11 +276,13 @@ class SchemaBuilder( } enhancedRegistry.add(schemaDefinition.transform { - it.operationTypeDefinition(OperationTypeDefinition - .newOperationTypeDefinition() - .name("query") - .typeName(TypeName("Query")) - .build()) + it.operationTypeDefinition( + OperationTypeDefinition + .newOperationTypeDefinition() + .name("query") + .typeName(TypeName("Query")) + .build() + ) }) } @@ -276,7 +300,11 @@ class SchemaBuilder( return typeDefinitionRegistry } - private fun addInputType(typeDefinitionRegistry: TypeDefinitionRegistry, inputName: String, relevantFields: List): String { + private fun addInputType( + typeDefinitionRegistry: TypeDefinitionRegistry, + inputName: String, + relevantFields: List + ): String { if (typeDefinitionRegistry.getType(inputName).isPresent) { return inputName } diff --git a/core/src/main/kotlin/org/neo4j/graphql/SchemaConfig.kt b/core/src/main/kotlin/org/neo4j/graphql/SchemaConfig.kt index e5802788..40aa6282 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/SchemaConfig.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/SchemaConfig.kt @@ -1,34 +1,34 @@ package org.neo4j.graphql data class SchemaConfig @JvmOverloads constructor( - val query: CRUDConfig = CRUDConfig(), - val mutation: CRUDConfig = CRUDConfig(), - - /** - * if true, the top level fields of the Query-type will be capitalized - */ - val capitalizeQueryFields: Boolean = false, - - /** - * if true, the generated fields for query or mutation will use the plural of the types name - */ - val pluralizeFields: Boolean = false, - - /** - * Defines the way the input for queries and mutations are generated - */ - val queryOptionStyle: InputStyle = InputStyle.ARGUMENT_PER_FIELD, - - /** - * if enabled the `filter` argument will be named `where` and the input type will be named `Where`. - * additionally, the separated filter arguments will no longer be generated. - */ - val useWhereFilter: Boolean = false, - - /** - * if enabled the `Date`, `Time`, `LocalTime`, `DateTime` and `LocalDateTime` are used as scalars - */ - val useTemporalScalars: Boolean = false, + val query: CRUDConfig = CRUDConfig(), + val mutation: CRUDConfig = CRUDConfig(), + + /** + * if true, the top level fields of the Query-type will be capitalized + */ + val capitalizeQueryFields: Boolean = false, + + /** + * if true, the generated fields for query or mutation will use the plural of the types name + */ + val pluralizeFields: Boolean = false, + + /** + * Defines the way the input for queries and mutations are generated + */ + val queryOptionStyle: InputStyle = InputStyle.ARGUMENT_PER_FIELD, + + /** + * if enabled the `filter` argument will be named `where` and the input type will be named `Where`. + * additionally, the separated filter arguments will no longer be generated. + */ + val useWhereFilter: Boolean = false, + + /** + * if enabled the `Date`, `Time`, `LocalTime`, `DateTime` and `LocalDateTime` are used as scalars + */ + val useTemporalScalars: Boolean = false, ) { data class CRUDConfig(val enabled: Boolean = true, val exclude: List = emptyList()) @@ -36,7 +36,10 @@ data class SchemaConfig @JvmOverloads constructor( /** * Separate arguments are generated for the query and / or mutation fields */ - @Deprecated(message = "Will be removed in the next major release", replaceWith = ReplaceWith(expression = "INPUT_TYPE")) + @Deprecated( + message = "Will be removed in the next major release", + replaceWith = ReplaceWith(expression = "INPUT_TYPE") + ) ARGUMENT_PER_FIELD, /** diff --git a/core/src/main/kotlin/org/neo4j/graphql/Translator.kt b/core/src/main/kotlin/org/neo4j/graphql/Translator.kt index e14c0e88..3aa163ae 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/Translator.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/Translator.kt @@ -12,7 +12,11 @@ class Translator(val schema: GraphQLSchema) { @JvmOverloads @Throws(OptimizedQueryException::class) - fun translate(query: String, params: Map = emptyMap(), ctx: QueryContext = QueryContext()): List { + fun translate( + query: String, + params: Map = emptyMap(), + ctx: QueryContext = QueryContext() + ): List { val cypherHolder = CypherHolder() val executionInput = ExecutionInput.newExecutionInput() .query(query) @@ -28,7 +32,7 @@ class Translator(val schema: GraphQLSchema) { is TypeMismatchError, // expected since we return cypher here instead of the correct json is NonNullableFieldWasNullError, // expected since the returned cypher does not match the shape of the graphql type is SerializationError // expected since the returned cypher does not match the shape of the graphql type - -> { + -> { // ignore } // generic error handling diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/AugmentFieldHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/AugmentFieldHandler.kt index 9ce6e017..9e095a09 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/AugmentFieldHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/AugmentFieldHandler.kt @@ -10,13 +10,15 @@ import org.neo4j.graphql.handler.projection.ProjectionBase * This class augments existing fields on a type and adds filtering and sorting to these fields. */ class AugmentFieldHandler( - schemaConfig: SchemaConfig, - typeDefinitionRegistry: TypeDefinitionRegistry, - neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + schemaConfig: SchemaConfig, + typeDefinitionRegistry: TypeDefinitionRegistry, + neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) : AugmentationHandler(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) { override fun augmentType(type: ImplementingTypeDefinition<*>) { - val enhanceRelations = { type.fieldDefinitions.map { fieldDef -> fieldDef.transform { augmentRelation(it, fieldDef) } } } + val enhanceRelations = { + type.fieldDefinitions.map { fieldDef -> fieldDef.transform { augmentRelation(it, fieldDef) } } + } val rewritten = when (type) { is ObjectTypeDefinition -> type.transform { it.fieldDefinitions(enhanceRelations()) } @@ -56,7 +58,10 @@ class AugmentFieldHandler( fieldBuilder.inputValueDefinition(input(ProjectionBase.ORDER_BY, orderType)) } } - if (!schemaConfig.useWhereFilter && schemaConfig.query.enabled && !schemaConfig.query.exclude.contains(fieldType.name)) { + if (!schemaConfig.useWhereFilter + && schemaConfig.query.enabled + && !schemaConfig.query.exclude.contains(fieldType.name) + ) { // legacy support val relevantFields = fieldType .getScalarFields() @@ -76,6 +81,9 @@ class AugmentFieldHandler( } - override fun createDataFetcher(operationType: OperationType, fieldDefinition: FieldDefinition): DataFetcher? = null + override fun createDataFetcher( + operationType: OperationType, + fieldDefinition: FieldDefinition + ): DataFetcher? = null } diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/BaseDataFetcher.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/BaseDataFetcher.kt index 49aeb138..b8f423d1 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/BaseDataFetcher.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/BaseDataFetcher.kt @@ -21,17 +21,18 @@ abstract class BaseDataFetcher(schemaConfig: SchemaConfig) : ProjectionBase(sche override fun get(env: DataFetchingEnvironment): Cypher { val field = env.mergedField?.singleField - ?: throw IllegalAccessException("expect one filed in environment.mergedField") + ?: throw IllegalAccessException("expect one filed in environment.mergedField") val variable = field.aliasOrName().decapitalize() prepareDataFetcher(env.fieldDefinition, env.parentType) val statement = generateCypher(variable, field, env) val dialect = env.queryContext().neo4jDialect - val query = Renderer.getRenderer(Configuration - .newConfig() - .withIndentStyle(Configuration.IndentStyle.TAB) - .withPrettyPrint(true) - .withDialect(dialect) - .build() + val query = Renderer.getRenderer( + Configuration + .newConfig() + .withIndentStyle(Configuration.IndentStyle.TAB) + .withPrettyPrint(true) + .withDialect(dialect) + .build() ).render(statement) val params = statement.catalog.parameters.mapValues { (_, value) -> diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/BaseDataFetcherForContainer.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/BaseDataFetcherForContainer.kt index cd5b132d..724ff2ef 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/BaseDataFetcherForContainer.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/BaseDataFetcherForContainer.kt @@ -22,7 +22,7 @@ abstract class BaseDataFetcherForContainer(schemaConfig: SchemaConfig) : BaseDat override fun initDataFetcher(fieldDefinition: GraphQLFieldDefinition, parentType: GraphQLType) { type = fieldDefinition.type.inner() as? GraphQLFieldsContainer - ?: throw IllegalStateException("expect type of field ${parentType.name()}.${fieldDefinition.name} to be GraphQLFieldsContainer, but was ${fieldDefinition.type.name()}") + ?: throw IllegalStateException("expect type of field ${parentType.name()}.${fieldDefinition.name} to be GraphQLFieldsContainer, but was ${fieldDefinition.type.name()}") fieldDefinition .arguments .filterNot { listOf(FIRST, OFFSET, ORDER_BY, NATIVE_ID, OPTIONS).contains(it.name) } @@ -36,17 +36,19 @@ abstract class BaseDataFetcherForContainer(schemaConfig: SchemaConfig) : BaseDat val dynamicPrefix = field.dynamicPrefix() propertyFields[field.name] = when { dynamicPrefix != null -> dynamicPrefixCallback(field, dynamicPrefix) - field.isNeo4jType() || (schemaConfig.useTemporalScalars && field.isNeo4jTemporalType()) -> neo4jTypeCallback(field) + field.isNeo4jType() || (schemaConfig.useTemporalScalars && field.isNeo4jTemporalType()) -> { + neo4jTypeCallback(field) + } else -> defaultCallback(field) } } } private fun defaultCallback(field: GraphQLFieldDefinition) = - { value: Any? -> - val propertyName = field.propertyName() - listOf(PropertyAccessor(propertyName) { variable -> queryParameter(value, variable, field.name) }) - } + { value: Any? -> + val propertyName = field.propertyName() + listOf(PropertyAccessor(propertyName) { variable -> queryParameter(value, variable, field.name) }) + } private fun neo4jTypeCallback(field: GraphQLFieldDefinition): (Any) -> List { val converter = getNeo4jTypeConverter(field) @@ -54,20 +56,20 @@ abstract class BaseDataFetcherForContainer(schemaConfig: SchemaConfig) : BaseDat } private fun dynamicPrefixCallback(field: GraphQLFieldDefinition, dynamicPrefix: String) = - { value: Any -> - // maps each property of the map to the node - (value as? Map<*, *>)?.map { (key, value) -> - PropertyAccessor( - "$dynamicPrefix${key}" - ) { variable -> queryParameter(value, variable, "${field.name}${(key as String).capitalize()}") } - } + { value: Any -> + // maps each property of the map to the node + (value as? Map<*, *>)?.map { (key, value) -> + PropertyAccessor( + "$dynamicPrefix${key}" + ) { variable -> queryParameter(value, variable, "${field.name}${(key as String).capitalize()}") } } + } protected fun properties(variable: String, arguments: Map): Array = - preparePredicateArguments(arguments) - .flatMap { listOf(it.propertyName, it.toExpression(variable)) } - .toTypedArray() + preparePredicateArguments(arguments) + .flatMap { listOf(it.propertyName, it.toExpression(variable)) } + .toTypedArray() private fun preparePredicateArguments(arguments: Map): List { val predicates = arguments @@ -85,11 +87,11 @@ abstract class BaseDataFetcherForContainer(schemaConfig: SchemaConfig) : BaseDat companion object { fun getSelectQuery( - variable: String, - label: String?, - idProperty: Argument?, - idField: GraphQLFieldDefinition, - isRelation: Boolean + variable: String, + label: String?, + idProperty: Argument?, + idField: GraphQLFieldDefinition, + isRelation: Boolean ): Pair { return when { idProperty != null -> { @@ -114,6 +116,7 @@ abstract class BaseDataFetcherForContainer(schemaConfig: SchemaConfig) : BaseDat } } } + else -> throw IllegalArgumentException("Could not generate selection for ${if (isRelation) "Relation" else "Node"} $label b/c of missing ID field") } } @@ -124,8 +127,8 @@ abstract class BaseDataFetcherForContainer(schemaConfig: SchemaConfig) : BaseDat * @param accessorFactory a factory for crating an expression to access the property */ class PropertyAccessor( - val propertyName: String, - private val accessorFactory: (variable: String) -> Expression + val propertyName: String, + private val accessorFactory: (variable: String) -> Expression ) { fun toExpression(variable: String): Expression { diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/CreateTypeHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/CreateTypeHandler.kt index 90bdbbe4..5aac3b2a 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/CreateTypeHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/CreateTypeHandler.kt @@ -17,9 +17,10 @@ import org.neo4j.graphql.* */ class CreateTypeHandler private constructor(schemaConfig: SchemaConfig) : BaseDataFetcherForContainer(schemaConfig) { - class Factory(schemaConfig: SchemaConfig, - typeDefinitionRegistry: TypeDefinitionRegistry, - neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + class Factory( + schemaConfig: SchemaConfig, + typeDefinitionRegistry: TypeDefinitionRegistry, + neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) : AugmentationHandler(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) { override fun augmentType(type: ImplementingTypeDefinition<*>) { @@ -33,7 +34,10 @@ class CreateTypeHandler private constructor(schemaConfig: SchemaConfig) : BaseDa addMutationField(fieldDefinition) } - override fun createDataFetcher(operationType: OperationType, fieldDefinition: FieldDefinition): DataFetcher? { + override fun createDataFetcher( + operationType: OperationType, + fieldDefinition: FieldDefinition + ): DataFetcher? { if (operationType != OperationType.MUTATION) { return null } diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/CypherDirectiveHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/CypherDirectiveHandler.kt index 223a635f..a739e958 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/CypherDirectiveHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/CypherDirectiveHandler.kt @@ -14,12 +14,16 @@ import org.neo4j.graphql.* */ class CypherDirectiveHandler(schemaConfig: SchemaConfig) : BaseDataFetcher(schemaConfig) { - class Factory(schemaConfig: SchemaConfig, - typeDefinitionRegistry: TypeDefinitionRegistry, - neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + class Factory( + schemaConfig: SchemaConfig, + typeDefinitionRegistry: TypeDefinitionRegistry, + neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) : AugmentationHandler(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) { - override fun createDataFetcher(operationType: OperationType, fieldDefinition: FieldDefinition): DataFetcher? { + override fun createDataFetcher( + operationType: OperationType, + fieldDefinition: FieldDefinition + ): DataFetcher? { fieldDefinition.cypherDirective() ?: return null return CypherDirectiveHandler(schemaConfig) } @@ -29,7 +33,7 @@ class CypherDirectiveHandler(schemaConfig: SchemaConfig) : BaseDataFetcher(schem val fieldDefinition = env.fieldDefinition val type = fieldDefinition.type.inner() as? GraphQLFieldsContainer val cypherDirective = fieldDefinition.cypherDirective() - ?: throw IllegalStateException("Expect field ${env.logField()} to have @cypher directive present") + ?: throw IllegalStateException("Expect field ${env.logField()} to have @cypher directive present") val node = org.neo4j.cypherdsl.core.Cypher.anyNode(variable) val ctxVariable = node.requiredSymbolicName diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/DeleteHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/DeleteHandler.kt index 5335f216..b0089fb6 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/DeleteHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/DeleteHandler.kt @@ -23,9 +23,10 @@ class DeleteHandler private constructor(schemaConfig: SchemaConfig) : BaseDataFe private lateinit var idField: GraphQLFieldDefinition private var isRelation: Boolean = false - class Factory(schemaConfig: SchemaConfig, - typeDefinitionRegistry: TypeDefinitionRegistry, - neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + class Factory( + schemaConfig: SchemaConfig, + typeDefinitionRegistry: TypeDefinitionRegistry, + neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) : AugmentationHandler(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) { override fun augmentType(type: ImplementingTypeDefinition<*>) { @@ -41,7 +42,10 @@ class DeleteHandler private constructor(schemaConfig: SchemaConfig) : BaseDataFe addMutationField(fieldDefinition) } - override fun createDataFetcher(operationType: OperationType, fieldDefinition: FieldDefinition): DataFetcher? { + override fun createDataFetcher( + operationType: OperationType, + fieldDefinition: FieldDefinition + ): DataFetcher? { if (operationType != OperationType.MUTATION) { return null } @@ -80,12 +84,12 @@ class DeleteHandler private constructor(schemaConfig: SchemaConfig) : BaseDataFe val (propertyContainer, where) = getSelectQuery(variable, type.label(), idArg, idField, isRelation) val select = if (isRelation) { val rel = propertyContainer as? Relationship - ?: throw IllegalStateException("Expect a Relationship but got ${propertyContainer.javaClass.name}") + ?: throw IllegalStateException("Expect a Relationship but got ${propertyContainer.javaClass.name}") org.neo4j.cypherdsl.core.Cypher.match(rel) .where(where) } else { val node = propertyContainer as? Node - ?: throw IllegalStateException("Expect a Node but got ${propertyContainer.javaClass.name}") + ?: throw IllegalStateException("Expect a Node but got ${propertyContainer.javaClass.name}") org.neo4j.cypherdsl.core.Cypher.match(node) .where(where) } diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/MergeOrUpdateHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/MergeOrUpdateHandler.kt index d2ef33c9..291258ad 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/MergeOrUpdateHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/MergeOrUpdateHandler.kt @@ -17,14 +17,16 @@ import org.neo4j.graphql.* * This class handles all the logic related to the updating of nodes. * This includes the augmentation of the update<Node> and merge<Node>-mutator and the related cypher generation */ -class MergeOrUpdateHandler private constructor(private val merge: Boolean, schemaConfig: SchemaConfig) : BaseDataFetcherForContainer(schemaConfig) { +class MergeOrUpdateHandler private constructor(private val merge: Boolean, schemaConfig: SchemaConfig) : + BaseDataFetcherForContainer(schemaConfig) { private lateinit var idField: GraphQLFieldDefinition private var isRelation: Boolean = false - class Factory(schemaConfig: SchemaConfig, - typeDefinitionRegistry: TypeDefinitionRegistry, - neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + class Factory( + schemaConfig: SchemaConfig, + typeDefinitionRegistry: TypeDefinitionRegistry, + neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) : AugmentationHandler(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) { override fun augmentType(type: ImplementingTypeDefinition<*>) { @@ -34,18 +36,31 @@ class MergeOrUpdateHandler private constructor(private val merge: Boolean, schem val relevantFields = type.getScalarFields() val idField = type.getIdField() - ?: throw IllegalStateException("Cannot resolve id field for type ${type.name}") - - val mergeField = buildFieldDefinition("merge", type, relevantFields, nullableResult = false, forceOptionalProvider = { it != idField }) + ?: throw IllegalStateException("Cannot resolve id field for type ${type.name}") + + val mergeField = buildFieldDefinition( + "merge", + type, + relevantFields, + nullableResult = false, + forceOptionalProvider = { it != idField }) .build() addMutationField(mergeField) - val updateField = buildFieldDefinition("update", type, relevantFields, nullableResult = true, forceOptionalProvider = { it != idField }) + val updateField = buildFieldDefinition( + "update", + type, + relevantFields, + nullableResult = true, + forceOptionalProvider = { it != idField }) .build() addMutationField(updateField) } - override fun createDataFetcher(operationType: OperationType, fieldDefinition: FieldDefinition): DataFetcher? { + override fun createDataFetcher( + operationType: OperationType, + fieldDefinition: FieldDefinition + ): DataFetcher? { if (operationType != OperationType.MUTATION) { return null } @@ -98,7 +113,7 @@ class MergeOrUpdateHandler private constructor(private val merge: Boolean, schem val select = if (isRelation) { val rel = propertyContainer as? Relationship - ?: throw IllegalStateException("Expect a Relationship but got ${propertyContainer.javaClass.name}") + ?: throw IllegalStateException("Expect a Relationship but got ${propertyContainer.javaClass.name}") if (merge && !idField.isNativeId()) { org.neo4j.cypherdsl.core.Cypher.merge(rel) // where is skipped since it does not make sense on merge @@ -107,7 +122,7 @@ class MergeOrUpdateHandler private constructor(private val merge: Boolean, schem } } else { val node = propertyContainer as? Node - ?: throw IllegalStateException("Expect a Node but got ${propertyContainer.javaClass.name}") + ?: throw IllegalStateException("Expect a Node but got ${propertyContainer.javaClass.name}") if (merge && !idField.isNativeId()) { org.neo4j.cypherdsl.core.Cypher.merge(node) // where is skipped since it does not make sense on merge diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/QueryHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/QueryHandler.kt index 59a41c8f..139e42bb 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/QueryHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/QueryHandler.kt @@ -16,9 +16,10 @@ import org.neo4j.graphql.handler.filter.OptimizedFilterHandler */ class QueryHandler private constructor(schemaConfig: SchemaConfig) : BaseDataFetcherForContainer(schemaConfig) { - class Factory(schemaConfig: SchemaConfig, - typeDefinitionRegistry: TypeDefinitionRegistry, - neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + class Factory( + schemaConfig: SchemaConfig, + typeDefinitionRegistry: TypeDefinitionRegistry, + neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) : AugmentationHandler(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) { override fun augmentType(type: ImplementingTypeDefinition<*>) { @@ -63,7 +64,10 @@ class QueryHandler private constructor(schemaConfig: SchemaConfig) : BaseDataFet addQueryField(def) } - override fun createDataFetcher(operationType: OperationType, fieldDefinition: FieldDefinition): DataFetcher? { + override fun createDataFetcher( + operationType: OperationType, + fieldDefinition: FieldDefinition + ): DataFetcher? { if (operationType != OperationType.QUERY) { return null } @@ -112,15 +116,23 @@ class QueryHandler private constructor(schemaConfig: SchemaConfig) : BaseDataFet .let { node -> node to match(node) } } - val ongoingReading = if (env.queryContext().optimizedQuery?.contains(QueryContext.OptimizationStrategy.FILTER_AS_MATCH) == true) { + val ongoingReading = + if (env.queryContext().optimizedQuery?.contains(QueryContext.OptimizationStrategy.FILTER_AS_MATCH) == true) { - OptimizedFilterHandler(type, schemaConfig).generateFilterQuery(variable, fieldDefinition, env.arguments, match, propertyContainer, env.variables) + OptimizedFilterHandler(type, schemaConfig).generateFilterQuery( + variable, + fieldDefinition, + env.arguments, + match, + propertyContainer, + env.variables + ) - } else { + } else { - val where = where(propertyContainer, fieldDefinition, type, env.arguments, env.variables) - match.where(where) - } + val where = where(propertyContainer, fieldDefinition, type, env.arguments, env.variables) + match.where(where) + } val (projectionEntries, subQueries) = projectFields(propertyContainer, type, env) val mapProjection = propertyContainer.project(projectionEntries).`as`(field.aliasOrName()) diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/filter/OptimizedFilterHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/filter/OptimizedFilterHandler.kt index b704a9a2..b13cfcaa 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/filter/OptimizedFilterHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/filter/OptimizedFilterHandler.kt @@ -14,8 +14,8 @@ import org.neo4j.graphql.parser.RelationPredicate typealias WhereClauseFactory = ( - queryWithoutWhere: OrderableOngoingReadingAndWithWithoutWhere, - names: List + queryWithoutWhere: OrderableOngoingReadingAndWithWithoutWhere, + names: List ) -> OrderableOngoingReadingAndWithWithWhere typealias ConditionBuilder = (ExposesWith) -> OrderableOngoingReadingAndWithWithoutWhere @@ -28,9 +28,17 @@ typealias ConditionBuilder = (ExposesWith) -> OrderableOngoingReadingAndWithWith * If this handler cannot generate an optimization for the passed filter, an [OptimizedQueryException] will be * thrown, so the calling site can fall back to the non-optimized logic */ -class OptimizedFilterHandler(val type: GraphQLFieldsContainer, schemaConfig: SchemaConfig) : ProjectionBase(schemaConfig) { - - fun generateFilterQuery(variable: String, fieldDefinition: GraphQLFieldDefinition, arguments: Map, readingWithoutWhere: OngoingReadingWithoutWhere, rootNode: PropertyContainer, variables: Map): OngoingReading { +class OptimizedFilterHandler(val type: GraphQLFieldsContainer, schemaConfig: SchemaConfig) : + ProjectionBase(schemaConfig) { + + fun generateFilterQuery( + variable: String, + fieldDefinition: GraphQLFieldDefinition, + arguments: Map, + readingWithoutWhere: OngoingReadingWithoutWhere, + rootNode: PropertyContainer, + variables: Map + ): OngoingReading { if (type.isRelationType()) { throw OptimizedQueryException("Optimization for relationship entity type is not implemented. Please provide a test case to help adding further cases.") } @@ -49,12 +57,13 @@ class OptimizedFilterHandler(val type: GraphQLFieldsContainer, schemaConfig: Sch ?.let { it as Map<*, *> } ?.let { val parsedQuery = parseFilter(it, type) - NestingLevelHandler(parsedQuery, false, rootNode, variable, ongoingReading - ?: readingWithoutWhere, - type, it, linkedSetOf(rootNode.requiredSymbolicName), variables) + NestingLevelHandler( + parsedQuery, false, rootNode, variable, ongoingReading ?: readingWithoutWhere, + type, it, linkedSetOf(rootNode.requiredSymbolicName), variables + ) .parseFilter() } - ?: readingWithoutWhere + ?: readingWithoutWhere } /** @@ -67,19 +76,19 @@ class OptimizedFilterHandler(val type: GraphQLFieldsContainer, schemaConfig: Sch * @param parentPassThroughWiths all the nodes, required to be passed through via WITH */ inner class NestingLevelHandler( - private val parsedQuery: ParsedQuery, - private val useDistinct: Boolean, - private val current: PropertyContainer, - private val variablePrefix: String, - private val matchQueryWithoutWhere: OngoingReading, - private val type: GraphQLFieldsContainer, - private val value: Map<*, *>, - private val parentPassThroughWiths: Collection, - private val variables: Map + private val parsedQuery: ParsedQuery, + private val useDistinct: Boolean, + private val current: PropertyContainer, + private val variablePrefix: String, + private val matchQueryWithoutWhere: OngoingReading, + private val type: GraphQLFieldsContainer, + private val value: Map<*, *>, + private val parentPassThroughWiths: Collection, + private val variables: Map ) { private fun currentNode() = current as? Node - ?: throw OptimizedQueryException("Only filtering on nodes is currently supported by the OptimizedFilterHandler. Please provide a test case to help adding further cases.") + ?: throw OptimizedQueryException("Only filtering on nodes is currently supported by the OptimizedFilterHandler. Please provide a test case to help adding further cases.") /** * @param additionalConditions additional conditions to be applied to the where @@ -113,11 +122,12 @@ class OptimizedFilterHandler(val type: GraphQLFieldsContainer, schemaConfig: Sch return if (additionalConditions != null) { additionalConditions(matchQueryWithWhere) } else { - val withs = if (parsedQuery.relationPredicates.isNotEmpty() && parentPassThroughWiths.none { it == current.requiredSymbolicName }) { - parentPassThroughWiths + current.requiredSymbolicName - } else { - parentPassThroughWiths - } + val withs = + if (parsedQuery.relationPredicates.isNotEmpty() && parentPassThroughWiths.none { it == current.requiredSymbolicName }) { + parentPassThroughWiths + current.requiredSymbolicName + } else { + parentPassThroughWiths + } withClauseWithOptionalDistinct(matchQueryWithWhere, withs, useDistinct) } } @@ -154,51 +164,76 @@ class OptimizedFilterHandler(val type: GraphQLFieldsContainer, schemaConfig: Sch } private fun handleQuantifierPredicates( - query: OrderableOngoingReadingAndWithWithoutWhere, - relFilter: RelationPredicate, - levelPassThroughWiths: LinkedHashSet + query: OrderableOngoingReadingAndWithWithoutWhere, + relFilter: RelationPredicate, + levelPassThroughWiths: LinkedHashSet ): OrderableOngoingReadingAndWithWithoutWhere { val objectField = relFilter.value - val nestedParsedQuery = parseFilter(objectField as Map<*, *>, relFilter.fieldDefinition.type.getInnerFieldsContainer()) - val hasPredicates = nestedParsedQuery.fieldPredicates.isNotEmpty() || nestedParsedQuery.relationPredicates.isNotEmpty() + val nestedParsedQuery = + parseFilter(objectField as Map<*, *>, relFilter.fieldDefinition.type.getInnerFieldsContainer()) + val hasPredicates = + nestedParsedQuery.fieldPredicates.isNotEmpty() || nestedParsedQuery.relationPredicates.isNotEmpty() var queryWithoutWhere = query val relVariableName = normalizeName(variablePrefix, relFilter.normalizedName) val relVariable = relFilter.relNode.named(relVariableName) val readingWithoutWhere: OngoingReading = when (relFilter.op) { - RelationOperator.NONE -> queryWithoutWhere.optionalMatch(relFilter.relationshipInfo.createRelation(currentNode(), relVariable)) + RelationOperator.NONE -> queryWithoutWhere.optionalMatch( + relFilter.relationshipInfo.createRelation(currentNode(), relVariable) + ) + else -> when (hasPredicates) { - true -> queryWithoutWhere.match(relFilter.relationshipInfo.createRelation(currentNode(), relVariable)) + true -> queryWithoutWhere.match( + relFilter.relationshipInfo.createRelation( + currentNode(), + relVariable + ) + ) + else -> queryWithoutWhere } } val totalFilter = { totalFilter(relFilter, relVariableName) } val countFilter = { countFilter(relVariable, relVariableName) } - val additionalConditions = { filter: List>, whereClauseFactory: WhereClauseFactory -> - createAdditionalConditions(nestedParsedQuery, relVariable, levelPassThroughWiths, filter, whereClauseFactory) - } + val additionalConditions = + { filter: List>, whereClauseFactory: WhereClauseFactory -> + createAdditionalConditions( + nestedParsedQuery, + relVariable, + levelPassThroughWiths, + filter, + whereClauseFactory + ) + } - val nestingLevelHandler = NestingLevelHandler(nestedParsedQuery, true, relVariable, relVariableName, - readingWithoutWhere, relFilter.relationshipInfo.type, objectField, levelPassThroughWiths, variables) + val nestingLevelHandler = NestingLevelHandler( + nestedParsedQuery, true, relVariable, relVariableName, + readingWithoutWhere, relFilter.relationshipInfo.type, objectField, levelPassThroughWiths, variables + ) when (relFilter.op) { RelationOperator.SOME -> queryWithoutWhere = nestingLevelHandler.parseFilter() RelationOperator.EVERY -> queryWithoutWhere = nestingLevelHandler.parseFilter( - additionalConditions(listOf(totalFilter(), countFilter()), { withWithoutWhere, (total, count) -> - withWithoutWhere.where(total.isEqualTo(count)) - })) + additionalConditions(listOf(totalFilter(), countFilter()), { withWithoutWhere, (total, count) -> + withWithoutWhere.where(total.isEqualTo(count)) + }) + ) + RelationOperator.SINGLE -> queryWithoutWhere = nestingLevelHandler.parseFilter( - additionalConditions(listOf(totalFilter(), countFilter()), { withWithoutWhere, (total, count) -> - withWithoutWhere - .where(total.isEqualTo(count)) - .and(total.isEqualTo(Cypher.literalOf(1))) - })) + additionalConditions(listOf(totalFilter(), countFilter()), { withWithoutWhere, (total, count) -> + withWithoutWhere + .where(total.isEqualTo(count)) + .and(total.isEqualTo(Cypher.literalOf(1))) + }) + ) + RelationOperator.NONE -> queryWithoutWhere = nestingLevelHandler.parseFilter( - additionalConditions(listOf(countFilter()), { withWithoutWhere, (count) -> - withWithoutWhere - .where(count.isEqualTo(Cypher.literalOf(0))) - })) + additionalConditions(listOf(countFilter()), { withWithoutWhere, (count) -> + withWithoutWhere.where(count.isEqualTo(Cypher.literalOf(0))) + }) + ) + else -> throw IllegalStateException("${relFilter.op} should not be set for filed `${relFilter.fieldDefinition.name}` of type `$type`.") } return queryWithoutWhere @@ -211,7 +246,10 @@ class OptimizedFilterHandler(val type: GraphQLFieldsContainer, schemaConfig: Sch throw OptimizedQueryException("AND / OR filters are currently not implemented. Please provide a test case to help adding further cases.") } - private fun totalFilter(relationPredicate: RelationPredicate, relVariableName: String): Pair { + private fun totalFilter( + relationPredicate: RelationPredicate, + relVariableName: String + ): Pair { val totalRel = relationPredicate.relationshipInfo.createRelation(currentNode(), relationPredicate.relNode) val totalVar = normalizeName(relVariableName, "Total") val total = Cypher.size(totalRel).`as`(totalVar) @@ -225,11 +263,11 @@ class OptimizedFilterHandler(val type: GraphQLFieldsContainer, schemaConfig: Sch } private fun createAdditionalConditions( - query: ParsedQuery, - relVariable: Node, - passThroughWiths: LinkedHashSet, - filter: List>, - whereClauseFactory: WhereClauseFactory + query: ParsedQuery, + relVariable: Node, + passThroughWiths: LinkedHashSet, + filter: List>, + whereClauseFactory: WhereClauseFactory ): ConditionBuilder { return { exposesWith: ExposesWith -> var additionalWiths = emptyList() @@ -238,8 +276,8 @@ class OptimizedFilterHandler(val type: GraphQLFieldsContainer, schemaConfig: Sch } val withWithoutWhere = withClauseWithOptionalDistinct( - exposesWith, - passThroughWiths + additionalWiths + filter.map { it.second } + exposesWith, + passThroughWiths + additionalWiths + filter.map { it.second } ) val where = whereClauseFactory(withWithoutWhere, filter.map { it.first }) withClauseWithOptionalDistinct(where, passThroughWiths + additionalWiths) @@ -248,9 +286,10 @@ class OptimizedFilterHandler(val type: GraphQLFieldsContainer, schemaConfig: Sch } private fun withClauseWithOptionalDistinct( - exposesWith: ExposesWith, - withs: Collection, - useDistinct: Boolean = true) = when { + exposesWith: ExposesWith, + withs: Collection, + useDistinct: Boolean = true + ) = when { useDistinct && withs.size == 1 -> exposesWith.withDistinct(*withs.toTypedArray()) else -> exposesWith.with(*withs.toTypedArray()) } diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/projection/ProjectionBase.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/projection/ProjectionBase.kt index 0738dd9d..72578509 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/projection/ProjectionBase.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/projection/ProjectionBase.kt @@ -14,7 +14,7 @@ import org.neo4j.graphql.parser.QueryParser.parseFilter * This class contains the logic for projecting nodes and relations */ open class ProjectionBase( - protected val schemaConfig: SchemaConfig + protected val schemaConfig: SchemaConfig ) { companion object { @@ -66,9 +66,9 @@ open class ProjectionBase( fun filterFieldName() = if (schemaConfig.useWhereFilter) WHERE else FILTER private fun orderBy( - node: SymbolicName, - args: Map, - fieldDefinition: GraphQLFieldDefinition? + node: SymbolicName, + args: Map, + fieldDefinition: GraphQLFieldDefinition? ): List? = if (schemaConfig.queryOptionStyle == SchemaConfig.InputStyle.INPUT_TYPE) { extractSortFromOptions(node, args, fieldDefinition) } else { @@ -76,15 +76,15 @@ open class ProjectionBase( } private fun extractSortFromOptions( - node: SymbolicName, - args: Map, - fieldDefinition: GraphQLFieldDefinition? + node: SymbolicName, + args: Map, + fieldDefinition: GraphQLFieldDefinition? ): List? { val options = args[OPTIONS] as? Map<*, *> val defaultOptions = (fieldDefinition?.getArgument(OPTIONS)?.type as? GraphQLInputObjectType) val sortArray = (options?.get(SORT) - ?: defaultOptions?.getField(SORT)?.inputFieldDefaultValue?.value?.toJavaValue()) + ?: defaultOptions?.getField(SORT)?.inputFieldDefaultValue?.value?.toJavaValue()) as? List<*> ?: return null return sortArray @@ -97,13 +97,13 @@ open class ProjectionBase( } private fun extractSortFromArgs( - node: SymbolicName, - args: Map, - fieldDefinition: GraphQLFieldDefinition? + node: SymbolicName, + args: Map, + fieldDefinition: GraphQLFieldDefinition? ): List? { val orderBy = args[ORDER_BY] - ?: fieldDefinition?.getArgument(ORDER_BY)?.argumentDefaultValue?.value + ?: fieldDefinition?.getArgument(ORDER_BY)?.argumentDefaultValue?.value return orderBy ?.let { it -> when (it) { @@ -121,15 +121,15 @@ open class ProjectionBase( } private fun createSort(node: SymbolicName, property: String, direction: String) = - sort(node.property(property)) - .let { if (Sort.valueOf((direction).toUpperCase()) == Sort.ASC) it.ascending() else it.descending() } + sort(node.property(property)) + .let { if (Sort.valueOf((direction).toUpperCase()) == Sort.ASC) it.ascending() else it.descending() } fun where( - propertyContainer: PropertyContainer, - fieldDefinition: GraphQLFieldDefinition, - type: GraphQLFieldsContainer, - arguments: Map, - variables: Map + propertyContainer: PropertyContainer, + fieldDefinition: GraphQLFieldDefinition, + type: GraphQLFieldsContainer, + arguments: Map, + variables: Map ): Condition { val variable = propertyContainer.requiredSymbolicName.value @@ -150,19 +150,20 @@ open class ProjectionBase( } ?.let { parseFilter(it, type) } ?.let { - val filterCondition = handleQuery(normalizeName(filterFieldName, variable), "", propertyContainer, it, type, variables) + val filterCondition = + handleQuery(normalizeName(filterFieldName, variable), "", propertyContainer, it, type, variables) result.and(filterCondition) } - ?: result + ?: result } protected fun handleQuery( - variablePrefix: String, - variableSuffix: String, - propertyContainer: PropertyContainer, - parsedQuery: ParsedQuery, - type: GraphQLFieldsContainer, - variables: Map + variablePrefix: String, + variableSuffix: String, + propertyContainer: PropertyContainer, + parsedQuery: ParsedQuery, + type: GraphQLFieldsContainer, + variables: Map ): Condition { var result = parsedQuery.getFieldConditions(propertyContainer, variablePrefix, variableSuffix, schemaConfig) @@ -177,11 +178,17 @@ open class ProjectionBase( if (value !is Map<*, *>) { throw IllegalArgumentException("Only object values are supported for filtering on queried relation ${predicate.value}, but got ${value.javaClass.name}") } - if(type.isRelationType()) { - val targetNode = predicate.relNode.named(normalizeName(propertyContainer.requiredSymbolicName.value, predicate.relationshipInfo.endField.capitalize())) + if (type.isRelationType()) { + val targetNode = predicate.relNode.named( + normalizeName( + propertyContainer.requiredSymbolicName.value, + predicate.relationshipInfo.endField.capitalize() + ) + ) val relType = predicate.relationshipInfo.type val parsedQuery2 = parseFilter(value, relType) - val condition = handleQuery(targetNode.requiredSymbolicName.value, "", targetNode, parsedQuery2, relType, variables) + val condition = + handleQuery(targetNode.requiredSymbolicName.value, "", targetNode, parsedQuery2, relType, variables) result = result.and(condition) continue } @@ -195,12 +202,21 @@ open class ProjectionBase( RelationOperator.NONE -> none(cond) else -> null }?.let { - val targetNode = predicate.relNode.named(normalizeName(variablePrefix, predicate.relationshipInfo.typeName)) + val targetNode = + predicate.relNode.named(normalizeName(variablePrefix, predicate.relationshipInfo.typeName)) val relType = predicate.relationshipInfo.type val parsedQuery2 = parseFilter(value, relType) - val condition = handleQuery(targetNode.requiredSymbolicName.value, "", targetNode, parsedQuery2, relType, variables) + val condition = + handleQuery(targetNode.requiredSymbolicName.value, "", targetNode, parsedQuery2, relType, variables) var where = it - .`in`(listBasedOn(predicate.relationshipInfo.createRelation(propertyContainer as Node, targetNode)).returning(condition)) + .`in`( + listBasedOn( + predicate.relationshipInfo.createRelation( + propertyContainer as Node, + targetNode + ) + ).returning(condition) + ) .where(cond.asCondition()) if (predicate.op == RelationOperator.NOT) { where = where.not() @@ -212,16 +228,30 @@ open class ProjectionBase( fun handleLogicalOperator(value: Any?, classifier: String, variables: Map): Condition { val objectValue = value as? Map<*, *> - ?: throw IllegalArgumentException("Only object values are supported for logical operations, but got ${value?.javaClass?.name}") + ?: throw IllegalArgumentException("Only object values are supported for logical operations, but got ${value?.javaClass?.name}") val parsedNestedQuery = parseFilter(objectValue, type) - return handleQuery(variablePrefix + classifier, variableSuffix, propertyContainer, parsedNestedQuery, type, variables) + return handleQuery( + variablePrefix + classifier, + variableSuffix, + propertyContainer, + parsedNestedQuery, + type, + variables + ) } fun handleLogicalOperators(values: List<*>?, classifier: String): List { return when { values?.isNotEmpty() == true -> when { - values.size > 1 -> values.mapIndexed { index, value -> handleLogicalOperator(value, "${classifier}${index + 1}", variables) } + values.size > 1 -> values.mapIndexed { index, value -> + handleLogicalOperator( + value, + "${classifier}${index + 1}", + variables + ) + } + else -> values.map { value -> handleLogicalOperator(value, "", variables) } } @@ -235,24 +265,24 @@ open class ProjectionBase( } fun projectFields( - propertyContainer: PropertyContainer, - nodeType: GraphQLFieldsContainer, - env: DataFetchingEnvironment, - variable: SymbolicName = propertyContainer.requiredSymbolicName, - variableSuffix: String? = null, - selectionSet: DataFetchingFieldSelectionSet = env.selectionSet, + propertyContainer: PropertyContainer, + nodeType: GraphQLFieldsContainer, + env: DataFetchingEnvironment, + variable: SymbolicName = propertyContainer.requiredSymbolicName, + variableSuffix: String? = null, + selectionSet: DataFetchingFieldSelectionSet = env.selectionSet, ): Pair, List> { val selectedFields = selectionSet.immediateFields.distinct() return projectSelection(propertyContainer, variable, selectedFields, nodeType, env, variableSuffix) } private fun projectSelection( - propertyContainer: PropertyContainer, - variable: SymbolicName, - selection: List, - nodeType: GraphQLFieldsContainer, - env: DataFetchingEnvironment, - variableSuffix: String? + propertyContainer: PropertyContainer, + variable: SymbolicName, + selection: List, + nodeType: GraphQLFieldsContainer, + env: DataFetchingEnvironment, + variableSuffix: String? ): Pair, List> { // TODO just render fragments on valid types (Labels) by using cypher like this: // apoc.map.mergeList([ @@ -269,8 +299,10 @@ open class ProjectionBase( } projectField(propertyContainer, variable, it, nodeType, env, variableSuffix) } else { - projectField(propertyContainer, variable, it, it.objectTypes.firstOrNull() - ?: throw IllegalStateException("only one object type is supported"), env, variableSuffix) + projectField( + propertyContainer, variable, it, it.objectTypes.firstOrNull() + ?: throw IllegalStateException("only one object type is supported"), env, variableSuffix + ) } projections.addAll(pro) subQueries += sub @@ -279,7 +311,14 @@ open class ProjectionBase( && !handledFields.contains(TYPE_NAME)) && env.queryContext().queryTypeOfInterfaces ) { // for interfaces the typename is required to determine the correct implementation - val (pro, sub) = projectField(propertyContainer, variable, TYPE_NAME_SELECTED_FIELD, nodeType, env, variableSuffix) + val (pro, sub) = projectField( + propertyContainer, + variable, + TYPE_NAME_SELECTED_FIELD, + nodeType, + env, + variableSuffix + ) projections.addAll(pro) subQueries += sub } @@ -287,12 +326,12 @@ open class ProjectionBase( } private fun projectField( - propertyContainer: PropertyContainer, - variable: SymbolicName, - field: SelectedField, - type: GraphQLFieldsContainer, - env: DataFetchingEnvironment, - variableSuffix: String? + propertyContainer: PropertyContainer, + variable: SymbolicName, + field: SelectedField, + type: GraphQLFieldsContainer, + env: DataFetchingEnvironment, + variableSuffix: String? ): Pair, List> { val projections = mutableListOf() @@ -303,14 +342,20 @@ open class ProjectionBase( } else { val label = name("label") val parameter = queryParameter(type.getValidTypeLabels(env.graphQLSchema), variable.value, "validTypes") - projections += head(listWith(label).`in`(labels(propertyContainer as? Node - ?: throw IllegalStateException("Labels are only supported for nodes"))).where(label.`in`(parameter)).returning()) + projections += head( + listWith(label).`in`( + labels( + propertyContainer as? Node + ?: throw IllegalStateException("Labels are only supported for nodes") + ) + ).where(label.`in`(parameter)).returning() + ) } return projections to emptyList() } val fieldDefinition = type.getFieldDefinition(field.name) - ?: throw IllegalStateException("No field ${field.name} in ${type.name}") + ?: throw IllegalStateException("No field ${field.name} in ${type.name}") if (fieldDefinition.isIgnored()) { return projections to emptyList() } @@ -322,10 +367,18 @@ open class ProjectionBase( if (cypherDirective != null) { val ctxVariable = name(field.contextualize(variable)) - val innerSubQuery = cypherDirective(ctxVariable, fieldDefinition, field.arguments, cypherDirective, variable) + val innerSubQuery = + cypherDirective(ctxVariable, fieldDefinition, field.arguments, cypherDirective, variable) subQueries += if (isObjectField && !cypherDirective.passThrough) { val fieldObjectType = fieldDefinition.type.getInnerFieldsContainer() - val (fieldProjection, nestedSubQueries) = projectFields(anyNode(ctxVariable), fieldObjectType, env, ctxVariable, variableSuffix, field.selectionSet) + val (fieldProjection, nestedSubQueries) = projectFields( + anyNode(ctxVariable), + fieldObjectType, + env, + ctxVariable, + variableSuffix, + field.selectionSet + ) with(variable) .call(innerSubQuery) .withSubQueries(nestedSubQueries) @@ -352,7 +405,15 @@ open class ProjectionBase( if (fieldDefinition.isNeo4jType()) { projections += projectNeo4jObjectType(variable, field, fieldDefinition) } else { - val (pro, sub) = projectRelationship(propertyContainer, variable, field, fieldDefinition, type, env, variableSuffix) + val (pro, sub) = projectRelationship( + propertyContainer, + variable, + field, + fieldDefinition, + type, + env, + variableSuffix + ) projections += pro subQueries += sub } @@ -368,13 +429,13 @@ open class ProjectionBase( dynamicPrefix != null -> { val key = name("key") projections += call("apoc.map.fromPairs").withArgs( - listWith(key) - .`in`(call("keys").withArgs(variable).asFunction()) - .where(key.startsWith(literalOf(dynamicPrefix))) - .returning( - call("substring").withArgs(key, literalOf(dynamicPrefix.length)).asFunction(), - property(variable, key) - ) + listWith(key) + .`in`(call("keys").withArgs(variable).asFunction()) + .where(key.startsWith(literalOf(dynamicPrefix))) + .returning( + call("substring").withArgs(key, literalOf(dynamicPrefix.length)).asFunction(), + property(variable, key) + ) ) .asFunction() } @@ -388,7 +449,11 @@ open class ProjectionBase( return projections to subQueries } - private fun projectNeo4jObjectType(variable: SymbolicName, field: SelectedField, fieldDefinition: GraphQLFieldDefinition): Expression { + private fun projectNeo4jObjectType( + variable: SymbolicName, + field: SelectedField, + fieldDefinition: GraphQLFieldDefinition + ): Expression { val converter = getNeo4jTypeConverter(fieldDefinition) val projections = mutableListOf() field.selectionSet.immediateFields @@ -399,7 +464,13 @@ open class ProjectionBase( return mapOf(*projections.toTypedArray()) } - fun cypherDirective(ctxVariable: SymbolicName, fieldDefinition: GraphQLFieldDefinition, arguments: Map, cypherDirective: CypherDirective, thisValue: SymbolicName?): ResultStatement { + fun cypherDirective( + ctxVariable: SymbolicName, + fieldDefinition: GraphQLFieldDefinition, + arguments: Map, + cypherDirective: CypherDirective, + thisValue: SymbolicName? + ): ResultStatement { val args = sortedMapOf() if (thisValue != null) args["this"] = thisValue.`as`("this") arguments @@ -408,7 +479,9 @@ open class ProjectionBase( fieldDefinition.arguments .filterNot { SPECIAL_FIELDS.contains(it.name) } .filter { it.argumentDefaultValue.value != null && !args.containsKey(it.name) } - .forEach { args[it.name] = queryParameter(it.argumentDefaultValue.value, ctxVariable.value, it.name).`as`(it.name) } + .forEach { + args[it.name] = queryParameter(it.argumentDefaultValue.value, ctxVariable.value, it.name).`as`(it.name) + } var reading: OrderableOngoingReadingAndWithWithoutWhere? = null if (thisValue != null) { @@ -425,9 +498,9 @@ open class ProjectionBase( } fun OngoingReadingAndReturn.skipLimitOrder( - ctxVariable: SymbolicName, - fieldDefinition: GraphQLFieldDefinition, - arguments: Map + ctxVariable: SymbolicName, + fieldDefinition: GraphQLFieldDefinition, + arguments: Map ) = if (fieldDefinition.type.isList()) { val ordering = orderBy(ctxVariable, arguments, fieldDefinition) val orderedResult = ordering?.let { o -> this.orderBy(*o.toTypedArray()) } ?: this @@ -438,13 +511,13 @@ open class ProjectionBase( } private fun projectRelationship( - node: PropertyContainer, - variable: SymbolicName, - field: SelectedField, - fieldDefinition: GraphQLFieldDefinition, - parent: GraphQLFieldsContainer, - env: DataFetchingEnvironment, - variableSuffix: String? + node: PropertyContainer, + variable: SymbolicName, + field: SelectedField, + fieldDefinition: GraphQLFieldDefinition, + parent: GraphQLFieldsContainer, + env: DataFetchingEnvironment, + variableSuffix: String? ): Pair> { return when (parent.isRelationType()) { true -> projectRelationshipParent(node, variable, field, fieldDefinition, parent, env, variableSuffix) @@ -453,10 +526,10 @@ open class ProjectionBase( } private fun relationshipInfoInCorrectDirection( - fieldObjectType: GraphQLFieldsContainer, - relInfo0: RelationshipInfo, - parent: GraphQLFieldsContainer, - relDirectiveField: RelationshipInfo? + fieldObjectType: GraphQLFieldsContainer, + relInfo0: RelationshipInfo, + parent: GraphQLFieldsContainer, + relDirectiveField: RelationshipInfo? ): RelationshipInfo { val startField = fieldObjectType.getRelevantFieldDefinition(relInfo0.startField)!! val endField = fieldObjectType.getRelevantFieldDefinition(relInfo0.endField)!! @@ -464,24 +537,32 @@ open class ProjectionBase( val inverse = startFieldTypeName != parent.name || startFieldTypeName == endField.type.innerName() && relDirectiveField?.direction != relInfo0.direction - return if (inverse) relInfo0.copy(direction = relInfo0.direction.invert(), startField = relInfo0.endField, endField = relInfo0.startField) else relInfo0 + return if (inverse) relInfo0.copy( + direction = relInfo0.direction.invert(), + startField = relInfo0.endField, + endField = relInfo0.startField + ) else relInfo0 } private fun projectRelationshipParent( - propertyContainer: PropertyContainer, - variable: SymbolicName, - field: SelectedField, - fieldDefinition: GraphQLFieldDefinition, - parent: GraphQLFieldsContainer, - env: DataFetchingEnvironment, - variableSuffix: String? + propertyContainer: PropertyContainer, + variable: SymbolicName, + field: SelectedField, + fieldDefinition: GraphQLFieldDefinition, + parent: GraphQLFieldsContainer, + env: DataFetchingEnvironment, + variableSuffix: String? ): Pair> { val fieldObjectType = fieldDefinition.type.inner() as? GraphQLFieldsContainer - ?: throw IllegalArgumentException("field ${fieldDefinition.name} of type ${parent.name} is not an object (fields container) and can not be handled as relationship") + ?: throw IllegalArgumentException("field ${fieldDefinition.name} of type ${parent.name} is not an object (fields container) and can not be handled as relationship") return when (propertyContainer) { is Node -> { - val (projectionEntries, subQueries) = projectFields(propertyContainer, fieldObjectType, env, name(variable.value + (variableSuffix?.capitalize() - ?: "")), variableSuffix, field.selectionSet) + val (projectionEntries, subQueries) = projectFields( + propertyContainer, fieldObjectType, env, name( + variable.value + (variableSuffix?.capitalize() + ?: "") + ), variableSuffix, field.selectionSet + ) propertyContainer.project(projectionEntries) to subQueries } @@ -491,14 +572,14 @@ open class ProjectionBase( } private fun projectNodeFromRichRelationship( - parent: GraphQLFieldsContainer, - fieldDefinition: GraphQLFieldDefinition, - variable: SymbolicName, - field: SelectedField, - env: DataFetchingEnvironment + parent: GraphQLFieldsContainer, + fieldDefinition: GraphQLFieldDefinition, + variable: SymbolicName, + field: SelectedField, + env: DataFetchingEnvironment ): Pair> { val relInfo = parent.relationship() - ?: throw IllegalStateException(parent.name + " is not an relation type") + ?: throw IllegalStateException(parent.name + " is not an relation type") val node = node(fieldDefinition.type.name()).named(fieldDefinition.name) val (start, end, target) = when (fieldDefinition.name) { @@ -507,7 +588,13 @@ open class ProjectionBase( else -> throw IllegalArgumentException("type ${parent.name} does not have a matching field with name ${fieldDefinition.name}") } val rel = relInfo.createRelation(start, end, false, variable) - val (projectFields, subQueries) = projectFields(target, fieldDefinition.type as GraphQLFieldsContainer, env, target.requiredSymbolicName, selectionSet = field.selectionSet) + val (projectFields, subQueries) = projectFields( + target, + fieldDefinition.type as GraphQLFieldsContainer, + env, + target.requiredSymbolicName, + selectionSet = field.selectionSet + ) val match = with(variable) .match(rel) @@ -519,25 +606,33 @@ open class ProjectionBase( } private fun projectRichAndRegularRelationship( - variable: SymbolicName, - field: SelectedField, - fieldDefinition: GraphQLFieldDefinition, - parent: GraphQLFieldsContainer, - env: DataFetchingEnvironment + variable: SymbolicName, + field: SelectedField, + fieldDefinition: GraphQLFieldDefinition, + parent: GraphQLFieldsContainer, + env: DataFetchingEnvironment ): Pair> { val fieldType = fieldDefinition.type val nodeType = fieldType.getInnerFieldsContainer() // todo combine both nestings if rel-entity - val relDirectiveObject = (nodeType as? GraphQLDirectiveContainer)?.getAppliedDirective(DirectiveConstants.RELATION)?.let { RelationshipInfo.create(nodeType, it) } - val relDirectiveField = fieldDefinition.getAppliedDirective(DirectiveConstants.RELATION)?.let { RelationshipInfo.create(nodeType, it) } + val relDirectiveObject = + (nodeType as? GraphQLDirectiveContainer)?.getAppliedDirective(DirectiveConstants.RELATION) + ?.let { RelationshipInfo.create(nodeType, it) } + val relDirectiveField = fieldDefinition.getAppliedDirective(DirectiveConstants.RELATION) + ?.let { RelationshipInfo.create(nodeType, it) } val (relInfo0, isRelFromType) = - relDirectiveObject?.let { it to true } - ?: relDirectiveField?.let { it to false } - ?: throw IllegalStateException("Field $field needs an @relation directive") + relDirectiveObject?.let { it to true } + ?: relDirectiveField?.let { it to false } + ?: throw IllegalStateException("Field $field needs an @relation directive") - val relInfo = if (isRelFromType) relationshipInfoInCorrectDirection(nodeType, relInfo0, parent, relDirectiveField) else relInfo0 + val relInfo = if (isRelFromType) relationshipInfoInCorrectDirection( + nodeType, + relInfo0, + parent, + relDirectiveField + ) else relInfo0 val childVariable = field.contextualize(variable) val childVariableName = name(childVariable) @@ -551,7 +646,14 @@ open class ProjectionBase( else -> node(nodeType.name).named(childVariableName) to null } - val (projectionEntries, sub) = projectFields(endNodePattern, nodeType, env, childVariableName, variableSuffix, field.selectionSet) + val (projectionEntries, sub) = projectFields( + endNodePattern, + nodeType, + env, + childVariableName, + variableSuffix, + field.selectionSet + ) val withPassThrough = mutableListOf(endNodePattern.requiredSymbolicName) var relationship = relInfo.createRelation(anyNode(variable), endNodePattern) @@ -572,13 +674,18 @@ open class ProjectionBase( val ordering = orderBy(childVariableName, field.arguments, fieldDefinition) val skipLimit = SkipLimit(childVariable, field.arguments, fieldDefinition) reading = when { - ordering != null -> skipLimit.format(reading.with(*withPassThrough.toTypedArray()).orderBy(*ordering.toTypedArray())) + ordering != null -> skipLimit.format( + reading.with(*withPassThrough.toTypedArray()).orderBy(*ordering.toTypedArray()) + ) + skipLimit.applies() -> skipLimit.format(reading.with(*withPassThrough.toTypedArray())) else -> reading } - reading.withSubQueries(sub).returning(collect(childVariableName.project(projectionEntries)).`as`(childVariableName)) + reading.withSubQueries(sub) + .returning(collect(childVariableName.project(projectionEntries)).`as`(childVariableName)) } else { - reading.withSubQueries(sub).returning(childVariableName.project(projectionEntries).`as`(childVariableName)).limit(1) + reading.withSubQueries(sub).returning(childVariableName.project(projectionEntries).`as`(childVariableName)) + .limit(1) } return childVariableName to listOf(subQuery.build()) } @@ -610,17 +717,27 @@ open class ProjectionBase( return limit?.let { result.limit(it) } ?: result } - private fun convertArgument(variable: String, arguments: Map, fieldDefinition: GraphQLFieldDefinition?, name: String): Parameter<*>? { + private fun convertArgument( + variable: String, + arguments: Map, + fieldDefinition: GraphQLFieldDefinition?, + name: String + ): Parameter<*>? { val value = arguments[name] - ?: fieldDefinition?.getArgument(name)?.argumentDefaultValue?.value - ?: return null + ?: fieldDefinition?.getArgument(name)?.argumentDefaultValue?.value + ?: return null return queryParameter(value, variable, name) } - private fun convertOptionField(variable: String, options: Map<*, *>?, defaultOptions: GraphQLInputObjectType?, name: String): Parameter<*>? { + private fun convertOptionField( + variable: String, + options: Map<*, *>?, + defaultOptions: GraphQLInputObjectType?, + name: String + ): Parameter<*>? { val value = options?.get(name) - ?: defaultOptions?.getField(name)?.inputFieldDefaultValue?.value - ?: return null + ?: defaultOptions?.getField(name)?.inputFieldDefaultValue?.value + ?: return null return queryParameter(value, variable, name) } diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/relation/BaseRelationHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/relation/BaseRelationHandler.kt index 92116ce4..3f44cac9 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/relation/BaseRelationHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/relation/BaseRelationHandler.kt @@ -14,29 +14,30 @@ import org.neo4j.graphql.handler.BaseDataFetcherForContainer /** * This is a base class for all handler acting on relations / edges */ -abstract class BaseRelationHandler(private val prefix: String, schemaConfig: SchemaConfig) : BaseDataFetcherForContainer(schemaConfig) { +abstract class BaseRelationHandler(private val prefix: String, schemaConfig: SchemaConfig) : + BaseDataFetcherForContainer(schemaConfig) { lateinit var relation: RelationshipInfo lateinit var startId: RelatedField lateinit var endId: RelatedField data class RelatedField( - val argumentName: String, - val field: GraphQLFieldDefinition, - val declaringType: GraphQLFieldsContainer + val argumentName: String, + val field: GraphQLFieldDefinition, + val declaringType: GraphQLFieldsContainer ) abstract class BaseRelationFactory( - private val prefix: String, - schemaConfig: SchemaConfig, - typeDefinitionRegistry: TypeDefinitionRegistry, - neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + private val prefix: String, + schemaConfig: SchemaConfig, + typeDefinitionRegistry: TypeDefinitionRegistry, + neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) : AugmentationHandler(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) { protected fun buildFieldDefinition( - source: ImplementingTypeDefinition<*>, - targetField: FieldDefinition, - nullableResult: Boolean + source: ImplementingTypeDefinition<*>, + targetField: FieldDefinition, + nullableResult: Boolean ): FieldDefinition.Builder? { val (sourceIdField, _) = getRelationFields(source, targetField) ?: return null @@ -83,7 +84,10 @@ abstract class BaseRelationHandler(private val prefix: String, schemaConfig: Sch return true } - final override fun createDataFetcher(operationType: OperationType, fieldDefinition: FieldDefinition): DataFetcher? { + final override fun createDataFetcher( + operationType: OperationType, + fieldDefinition: FieldDefinition + ): DataFetcher? { if (operationType != OperationType.MUTATION) { return null } @@ -103,7 +107,7 @@ abstract class BaseRelationHandler(private val prefix: String, schemaConfig: Sch .removePrefix(p) .decapitalize() .let { sourceType.getFieldDefinition(it) } - ?: return null + ?: return null if (!canHandleField(targetField)) { return null } @@ -117,7 +121,7 @@ abstract class BaseRelationHandler(private val prefix: String, schemaConfig: Sch private fun ImplementingTypeDefinition<*>.hasRelationshipFor(name: String): Boolean { val field = getFieldDefinition(name) - ?: throw IllegalArgumentException("$name is not defined on ${this.name}") + ?: throw IllegalArgumentException("$name is not defined on ${this.name}") val fieldObjectType = field.type.inner().resolve() as? ImplementingTypeDefinition<*> ?: return false val target = getDirectiveArgument(DirectiveConstants.RELATION, DirectiveConstants.RELATION_TO, null) @@ -132,7 +136,10 @@ abstract class BaseRelationHandler(private val prefix: String, schemaConfig: Sch abstract fun createDataFetcher(): DataFetcher? - private fun getRelationFields(source: ImplementingTypeDefinition<*>, targetField: FieldDefinition): Pair? { + private fun getRelationFields( + source: ImplementingTypeDefinition<*>, + targetField: FieldDefinition + ): Pair? { val targetType = targetField.type.inner().resolve() as? ImplementingTypeDefinition<*> ?: return null val sourceIdField = source.getIdField() val targetIdField = targetType.getIdField() @@ -159,18 +166,19 @@ abstract class BaseRelationHandler(private val prefix: String, schemaConfig: Sch .removePrefix(p) .decapitalize() .let { - type.getRelevantFieldDefinition(it) ?: throw IllegalStateException("Cannot find field $it on type ${type.name}") + type.getRelevantFieldDefinition(it) + ?: throw IllegalStateException("Cannot find field $it on type ${type.name}") } relation = type.relationshipFor(targetField.name) - ?: throw IllegalStateException("Cannot resolve relationship for ${targetField.name} on type ${type.name}") + ?: throw IllegalStateException("Cannot resolve relationship for ${targetField.name} on type ${type.name}") val targetType = targetField.type.getInnerFieldsContainer() val sourceIdField = type.getIdField() - ?: throw IllegalStateException("Cannot find id field for type ${type.name}") + ?: throw IllegalStateException("Cannot find id field for type ${type.name}") val targetIdField = targetType.getIdField() - ?: throw IllegalStateException("Cannot find id field for type ${targetType.name}") + ?: throw IllegalStateException("Cannot find id field for type ${targetType.name}") startId = RelatedField(sourceIdField.name, sourceIdField, type) endId = RelatedField(targetField.name, targetIdField, targetType) } @@ -188,10 +196,12 @@ abstract class BaseRelationHandler(private val prefix: String, schemaConfig: Sch if (!arguments.containsKey(idField.argumentName)) { throw IllegalArgumentException("No ID for the ${if (start) "start" else "end"} Type provided, ${idField.argumentName} is required") } - val (rel, where) = getSelectQuery(relFieldName, idField.declaringType.label(), arguments[idField.argumentName], - idField.field, false) + val (rel, where) = getSelectQuery( + relFieldName, idField.declaringType.label(), arguments[idField.argumentName], + idField.field, false + ) return (rel as? Node - ?: throw IllegalStateException("Expected type to be of type node")) to where + ?: throw IllegalStateException("Expected type to be of type node")) to where } } diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/relation/CreateRelationHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/relation/CreateRelationHandler.kt index 1ca80440..2ca6f74c 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/relation/CreateRelationHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/relation/CreateRelationHandler.kt @@ -14,9 +14,10 @@ import org.neo4j.graphql.* */ class CreateRelationHandler private constructor(schemaConfig: SchemaConfig) : BaseRelationHandler("add", schemaConfig) { - class Factory(schemaConfig: SchemaConfig, - typeDefinitionRegistry: TypeDefinitionRegistry, - neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + class Factory( + schemaConfig: SchemaConfig, + typeDefinitionRegistry: TypeDefinitionRegistry, + neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) : BaseRelationFactory("add", schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) { override fun augmentType(type: ImplementingTypeDefinition<*>) { @@ -28,7 +29,13 @@ class CreateRelationHandler private constructor(schemaConfig: SchemaConfig) : Ba val richRelationTypes = typeDefinitionRegistry.types().values .filterIsInstance>() .filter { it.getDirective(DirectiveConstants.RELATION) != null } - .associate { it.getDirectiveArgument(DirectiveConstants.RELATION, DirectiveConstants.RELATION_NAME, null)!! to it.name } + .associate { + it.getDirectiveArgument( + DirectiveConstants.RELATION, + DirectiveConstants.RELATION_NAME, + null + )!! to it.name + } type.fieldDefinitions @@ -38,7 +45,11 @@ class CreateRelationHandler private constructor(schemaConfig: SchemaConfig) : Ba ?.let { builder -> val relationType = targetField - .getDirectiveArgument(DirectiveConstants.RELATION, DirectiveConstants.RELATION_NAME, null) + .getDirectiveArgument( + DirectiveConstants.RELATION, + DirectiveConstants.RELATION_NAME, + null + ) ?.let { it -> (richRelationTypes[it]) } ?.let { typeDefinitionRegistry.getUnwrappedType(it) as? ImplementingTypeDefinition } diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/relation/CreateRelationTypeHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/relation/CreateRelationTypeHandler.kt index e2794cf2..a41aa3b7 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/relation/CreateRelationTypeHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/relation/CreateRelationTypeHandler.kt @@ -14,11 +14,13 @@ import org.neo4j.graphql.* * This class handles all the logic related to the creation of relations. * This includes the augmentation of the create<Edge>-mutator and the related cypher generation */ -class CreateRelationTypeHandler private constructor(schemaConfig: SchemaConfig) : BaseRelationHandler("create", schemaConfig) { +class CreateRelationTypeHandler private constructor(schemaConfig: SchemaConfig) : + BaseRelationHandler("create", schemaConfig) { - class Factory(schemaConfig: SchemaConfig, - typeDefinitionRegistry: TypeDefinitionRegistry, - neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + class Factory( + schemaConfig: SchemaConfig, + typeDefinitionRegistry: TypeDefinitionRegistry, + neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) : AugmentationHandler(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) { override fun augmentType(type: ImplementingTypeDefinition<*>) { @@ -38,14 +40,17 @@ class CreateRelationTypeHandler private constructor(schemaConfig: SchemaConfig) .filter { it.name != endIdField.argumentName } val builder = - buildFieldDefinition("create", type, createArgs, nullableResult = false) - .inputValueDefinition(input(startIdField.argumentName, startIdField.field.type)) - .inputValueDefinition(input(endIdField.argumentName, endIdField.field.type)) + buildFieldDefinition("create", type, createArgs, nullableResult = false) + .inputValueDefinition(input(startIdField.argumentName, startIdField.field.type)) + .inputValueDefinition(input(endIdField.argumentName, endIdField.field.type)) addMutationField(builder.build()) } - override fun createDataFetcher(operationType: OperationType, fieldDefinition: FieldDefinition): DataFetcher? { + override fun createDataFetcher( + operationType: OperationType, + fieldDefinition: FieldDefinition + ): DataFetcher? { if (operationType != OperationType.MUTATION) { return null } @@ -92,17 +97,20 @@ class CreateRelationTypeHandler private constructor(schemaConfig: SchemaConfig) data class RelatedField( - val argumentName: String, - val field: FieldDefinition, + val argumentName: String, + val field: FieldDefinition, ) - private fun getRelatedIdField(info: RelationshipInfo>, relFieldName: String?): RelatedField? { + private fun getRelatedIdField( + info: RelationshipInfo>, + relFieldName: String? + ): RelatedField? { if (relFieldName == null) return null val relFieldDefinition = info.type.getFieldDefinition(relFieldName) - ?: throw IllegalArgumentException("field $relFieldName does not exists on ${info.typeName}") + ?: throw IllegalArgumentException("field $relFieldName does not exists on ${info.typeName}") val relType = relFieldDefinition.type.inner().resolve() as? ImplementingTypeDefinition<*> - ?: throw IllegalArgumentException("type ${relFieldDefinition.type.name()} not found") + ?: throw IllegalArgumentException("type ${relFieldDefinition.type.name()} not found") return relType.fieldDefinitions .filterNot { it.isIgnored() } .filter { it.type.inner().isID() } @@ -114,19 +122,19 @@ class CreateRelationTypeHandler private constructor(schemaConfig: SchemaConfig) private fun getRelatedIdField(info: RelationshipInfo, relFieldName: String): RelatedField { val relFieldDefinition = info.type.getRelevantFieldDefinition(relFieldName) - ?: throw IllegalArgumentException("field $relFieldName does not exists on ${info.typeName}") + ?: throw IllegalArgumentException("field $relFieldName does not exists on ${info.typeName}") val relType = relFieldDefinition.type.inner() as? GraphQLImplementingType - ?: throw IllegalArgumentException("type ${relFieldDefinition.type.name()} not found") + ?: throw IllegalArgumentException("type ${relFieldDefinition.type.name()} not found") return relType.getRelevantFieldDefinitions().filter { it.isID() } .map { RelatedField(normalizeFieldName(relFieldName, it.name), it, relType) } .firstOrNull() - ?: throw IllegalStateException("Cannot find id field for type ${info.typeName}") + ?: throw IllegalStateException("Cannot find id field for type ${info.typeName}") } override fun initRelation(fieldDefinition: GraphQLFieldDefinition) { relation = type.relationship() - ?: throw IllegalStateException("Cannot resolve relationship for type ${type.name}") + ?: throw IllegalStateException("Cannot resolve relationship for type ${type.name}") startId = getRelatedIdField(relation, relation.startField) endId = getRelatedIdField(relation, relation.endField) } diff --git a/core/src/main/kotlin/org/neo4j/graphql/handler/relation/DeleteRelationHandler.kt b/core/src/main/kotlin/org/neo4j/graphql/handler/relation/DeleteRelationHandler.kt index 839e4235..c0f5ebc9 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/handler/relation/DeleteRelationHandler.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/handler/relation/DeleteRelationHandler.kt @@ -15,11 +15,13 @@ import org.neo4j.graphql.withSubQueries * This class handles all the logic related to the deletion of relations starting from an existing node. * This includes the augmentation of the delete<Edge>-mutator and the related cypher generation */ -class DeleteRelationHandler private constructor(schemaConfig: SchemaConfig) : BaseRelationHandler("delete", schemaConfig) { +class DeleteRelationHandler private constructor(schemaConfig: SchemaConfig) : + BaseRelationHandler("delete", schemaConfig) { - class Factory(schemaConfig: SchemaConfig, - typeDefinitionRegistry: TypeDefinitionRegistry, - neo4jTypeDefinitionRegistry: TypeDefinitionRegistry + class Factory( + schemaConfig: SchemaConfig, + typeDefinitionRegistry: TypeDefinitionRegistry, + neo4jTypeDefinitionRegistry: TypeDefinitionRegistry ) : BaseRelationFactory("delete", schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry) { override fun augmentType(type: ImplementingTypeDefinition<*>) { diff --git a/core/src/main/kotlin/org/neo4j/graphql/parser/QueryParser.kt b/core/src/main/kotlin/org/neo4j/graphql/parser/QueryParser.kt index 97a73872..21931829 100644 --- a/core/src/main/kotlin/org/neo4j/graphql/parser/QueryParser.kt +++ b/core/src/main/kotlin/org/neo4j/graphql/parser/QueryParser.kt @@ -15,45 +15,56 @@ typealias CypherDSL = org.neo4j.cypherdsl.core.Cypher * An internal representation of all the filtering passed to a graphql field */ class ParsedQuery( - val fieldPredicates: List, - val relationPredicates: List, - val or: List<*>? = null, - val and: List<*>? = null + val fieldPredicates: List, + val relationPredicates: List, + val or: List<*>? = null, + val and: List<*>? = null ) { - fun getFieldConditions(propertyContainer: PropertyContainer, variablePrefix: String, variableSuffix: String, schemaConfig: SchemaConfig): Condition = - fieldPredicates - .flatMap { it.createCondition(propertyContainer, variablePrefix, variableSuffix, schemaConfig) } - .reduceOrNull { result, condition -> result.and(condition) } - ?: noCondition() + fun getFieldConditions( + propertyContainer: PropertyContainer, + variablePrefix: String, + variableSuffix: String, + schemaConfig: SchemaConfig + ): Condition = + fieldPredicates + .flatMap { it.createCondition(propertyContainer, variablePrefix, variableSuffix, schemaConfig) } + .reduceOrNull { result, condition -> result.and(condition) } + ?: noCondition() } abstract class Predicate( - val op: T, - val value: Any?, - val normalizedName: String, - val index: Int) + val op: T, + val value: Any?, + val normalizedName: String, + val index: Int +) /** * Predicates on a nodes' or relations' property */ class FieldPredicate( - op: FieldOperator, - value: Any?, - val fieldDefinition: GraphQLFieldDefinition, - index: Int + op: FieldOperator, + value: Any?, + val fieldDefinition: GraphQLFieldDefinition, + index: Int ) : Predicate(op, value, normalizeName(fieldDefinition.name, op.suffix.toCamelCase()), index) { - fun createCondition(propertyContainer: PropertyContainer, variablePrefix: String, variableSuffix: String, schemaConfig: SchemaConfig) = - op.resolveCondition( - variablePrefix, - normalizedName, - propertyContainer, - fieldDefinition, - value, - schemaConfig, - variableSuffix - ) + fun createCondition( + propertyContainer: PropertyContainer, + variablePrefix: String, + variableSuffix: String, + schemaConfig: SchemaConfig + ) = + op.resolveCondition( + variablePrefix, + normalizedName, + propertyContainer, + fieldDefinition, + value, + schemaConfig, + variableSuffix + ) } @@ -62,11 +73,11 @@ class FieldPredicate( */ class RelationPredicate( - type: GraphQLFieldsContainer, - op: RelationOperator, - value: Any?, - val fieldDefinition: GraphQLFieldDefinition, - index: Int + type: GraphQLFieldsContainer, + op: RelationOperator, + value: Any?, + val fieldDefinition: GraphQLFieldDefinition, + index: Int ) : Predicate(op, value, normalizeName(fieldDefinition.name, op.suffix.toCamelCase()), index) { val relationshipInfo = type.relationshipFor(fieldDefinition.name)!! @@ -75,8 +86,10 @@ class RelationPredicate( fun createRelation(start: Node): Relationship = relationshipInfo.createRelation(start, relNode) fun createExistsCondition(propertyContainer: PropertyContainer): Condition { - val relation = createRelation(propertyContainer as? Node - ?: throw IllegalStateException("""propertyContainer is expected to be a Node but was ${propertyContainer.javaClass.name}""")) + val relation = createRelation( + propertyContainer as? Node + ?: throw IllegalStateException("""propertyContainer is expected to be a Node but was ${propertyContainer.javaClass.name}""") + ) val condition = CypherDSL.match(relation).asCondition() return when (op) { RelationOperator.NOT -> condition @@ -98,11 +111,11 @@ object QueryParser { .toMap(mutableMapOf()) val or = queriedFields.remove("OR")?.second?.let { (it as? List<*>) - ?: throw IllegalArgumentException("OR on type `${type.name}` is expected to be a list") + ?: throw IllegalArgumentException("OR on type `${type.name}` is expected to be a list") } val and = queriedFields.remove("AND")?.second?.let { (it as? List<*>) - ?: throw IllegalArgumentException("AND on type `${type.name}` is expected to be a list") + ?: throw IllegalArgumentException("AND on type `${type.name}` is expected to be a list") } return createParsedQuery(queriedFields, type, or, and) } @@ -110,7 +123,11 @@ object QueryParser { /** * This parser takes all non-filter arguments of a graphql-field and transform it to the internal [ParsedQuery]-representation */ - fun parseArguments(arguments: Map, fieldDefinition: GraphQLFieldDefinition, type: GraphQLFieldsContainer): ParsedQuery { + fun parseArguments( + arguments: Map, + fieldDefinition: GraphQLFieldDefinition, + type: GraphQLFieldsContainer + ): ParsedQuery { // Map of all queried fields // we remove all matching fields from this map, so we can ensure that only known fields got queried val queriedFields = arguments @@ -131,10 +148,10 @@ object QueryParser { private fun createParsedQuery( - queriedFields: MutableMap>, - type: GraphQLFieldsContainer, - or: List<*>? = null, - and: List<*>? = null + queriedFields: MutableMap>, + type: GraphQLFieldsContainer, + or: List<*>? = null, + and: List<*>? = null ): ParsedQuery { // find all matching fields val fieldPredicates = mutableListOf() @@ -175,9 +192,9 @@ object QueryParser { } return ParsedQuery( - fieldPredicates.sortedBy(Predicate<*>::index), - relationPredicates.sortedBy(Predicate<*>::index), - or, and + fieldPredicates.sortedBy(Predicate<*>::index), + relationPredicates.sortedBy(Predicate<*>::index), + or, and ) } } diff --git a/core/src/test/kotlin/DataFetcherInterceptorDemo.kt b/core/src/test/kotlin/DataFetcherInterceptorDemo.kt index 49fca47a..fc290a01 100644 --- a/core/src/test/kotlin/DataFetcherInterceptorDemo.kt +++ b/core/src/test/kotlin/DataFetcherInterceptorDemo.kt @@ -26,7 +26,7 @@ fun initBoundSchema(schema: String): GraphQLSchema { } else { result.list().map { record -> record.get(variable).asObject() }.firstOrNull() - ?: emptyMap() + ?: emptyMap() } } } @@ -35,12 +35,14 @@ fun initBoundSchema(schema: String): GraphQLSchema { } fun main() { - @Language("GraphQL") val schema = initBoundSchema(""" + @Language("GraphQL") val schema = initBoundSchema( + """ type Movie { movieId: ID! title: String } - """.trimIndent()) + """.trimIndent() + ) val graphql = GraphQL.newGraphQL(schema).build() val movies = graphql.execute("{ movie { title }}") } diff --git a/core/src/test/kotlin/GraphQLServer.kt b/core/src/test/kotlin/GraphQLServer.kt index c6c92d67..73a2fdd4 100644 --- a/core/src/test/kotlin/GraphQLServer.kt +++ b/core/src/test/kotlin/GraphQLServer.kt @@ -48,7 +48,11 @@ fun main() { } as Map }.also { println(it) } - val driver = GraphDatabase.driver("bolt://localhost", AuthTokens.basic("neo4j", "test"), Config.builder().withoutEncryption().build()) + val driver = GraphDatabase.driver( + "bolt://localhost", + AuthTokens.basic("neo4j", "test"), + Config.builder().withoutEncryption().build() + ) val graphQLSchema = SchemaBuilder.buildSchema(schema, dataFetchingInterceptor = object : DataFetchingInterceptor { override fun fetchData(env: DataFetchingEnvironment, delegate: DataFetcher): Any? { @@ -59,8 +63,11 @@ fun main() { try { val result = session.run(cypher, Values.value(params)) when { - type?.isList() == true -> result.stream().map { it[variable].asObject() }.collect(Collectors.toList()) - else -> result.stream().map { it[variable].asObject() }.findFirst().orElse(emptyMap()) + type?.isList() == true -> result.stream().map { it[variable].asObject() } + .collect(Collectors.toList()) + + else -> result.stream().map { it[variable].asObject() }.findFirst() + .orElse(emptyMap()) } } catch (e: Exception) { e.printStackTrace() @@ -83,19 +90,24 @@ fun main() { schema.execute(query).let { println(mapper.writeValueAsString(it));it } } else { try { - val queryContext = QueryContext(optimizedQuery = setOf(QueryContext.OptimizationStrategy.FILTER_AS_MATCH)) - schema.execute(ExecutionInput - .newExecutionInput() - .query(query) - .graphQLContext(mapOf(QueryContext.KEY to queryContext)) - .variables(params(payload)) - .build()) + val queryContext = + QueryContext(optimizedQuery = setOf(QueryContext.OptimizationStrategy.FILTER_AS_MATCH)) + schema.execute( + ExecutionInput + .newExecutionInput() + .query(query) + .graphQLContext(mapOf(QueryContext.KEY to queryContext)) + .variables(params(payload)) + .build() + ) } catch (e: OptimizedQueryException) { - schema.execute(ExecutionInput - .newExecutionInput() - .query(query) - .variables(params(payload)) - .build()) + schema.execute( + ExecutionInput + .newExecutionInput() + .query(query) + .variables(params(payload)) + .build() + ) } } req.sendResponse(response) diff --git a/core/src/test/kotlin/org/neo4j/graphql/AugmentationTests.kt b/core/src/test/kotlin/org/neo4j/graphql/AugmentationTests.kt index 9341e312..b966c750 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/AugmentationTests.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/AugmentationTests.kt @@ -13,5 +13,6 @@ class AugmentationTests { fun `schema-operations-tests`() = GraphQLSchemaTestSuite("schema-operations-tests.adoc").generateTests() @TestFactory - fun `schema augmentation tests`() = createTestsInPath("tck-test-files/schema", { GraphQLSchemaTestSuite(it).generateTests() }) + fun `schema augmentation tests`() = + createTestsInPath("tck-test-files/schema", { GraphQLSchemaTestSuite(it).generateTests() }) } diff --git a/core/src/test/kotlin/org/neo4j/graphql/CypherTests.kt b/core/src/test/kotlin/org/neo4j/graphql/CypherTests.kt index 39e33391..319d2c0a 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/CypherTests.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/CypherTests.kt @@ -63,7 +63,8 @@ class CypherTests { fun `translator-tests3`() = CypherTestSuite("translator-tests3.adoc", neo4j).generateTests() @TestFactory - fun `translator-tests-custom-scalars`() = CypherTestSuite("translator-tests-custom-scalars.adoc", neo4j).generateTests() + fun `translator-tests-custom-scalars`() = + CypherTestSuite("translator-tests-custom-scalars.adoc", neo4j).generateTests() @TestFactory fun `optimized-query-for-filter`() = CypherTestSuite("optimized-query-for-filter.adoc", neo4j).generateTests() @@ -72,10 +73,11 @@ class CypherTests { fun `custom-fields`() = CypherTestSuite("custom-fields.adoc", neo4j).generateTests() @TestFactory - fun `test issues`() = createTestsInPath("issues", { CypherTestSuite(it, neo4j).generateTests() }) + fun `test issues`() = createTestsInPath("issues", { CypherTestSuite(it, neo4j).generateTests() }) @TestFactory - fun `new cypher tck tests`() = createTestsInPath("tck-test-files/cypher", { CypherTestSuite(it, neo4j).generateTests() }) + fun `new cypher tck tests`() = + createTestsInPath("tck-test-files/cypher", { CypherTestSuite(it, neo4j).generateTests() }) companion object { private val INTEGRATION_TESTS = System.getProperty("neo4j-graphql-java.integration-tests", "false") == "true" diff --git a/core/src/test/kotlin/org/neo4j/graphql/TranslatorExceptionTests.kt b/core/src/test/kotlin/org/neo4j/graphql/TranslatorExceptionTests.kt index 39b1057b..d2fbee80 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/TranslatorExceptionTests.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/TranslatorExceptionTests.kt @@ -17,26 +17,30 @@ class TranslatorExceptionTests : AsciiDocTestSuite("translator-tests1.adoc") { override fun schemaTestFactory(schema: String): List { val translator = Translator(SchemaBuilder.buildSchema(schema)) return listOf( - DynamicTest.dynamicTest("unknownType") { - Assertions.assertThrows(InvalidQueryException::class.java) { - translator.translate(""" + DynamicTest.dynamicTest("unknownType") { + Assertions.assertThrows(InvalidQueryException::class.java) { + translator.translate( + """ { company { name } } - """) - } - }, - DynamicTest.dynamicTest("mutation") { - Assertions.assertThrows(InvalidQueryException::class.java) { - translator.translate(""" + """ + ) + } + }, + DynamicTest.dynamicTest("mutation") { + Assertions.assertThrows(InvalidQueryException::class.java) { + translator.translate( + """ { createPerson() } - """.trimIndent()) - } + """.trimIndent() + ) } + } ) } diff --git a/core/src/test/kotlin/org/neo4j/graphql/utils/AsciiDocTestSuite.kt b/core/src/test/kotlin/org/neo4j/graphql/utils/AsciiDocTestSuite.kt index 0fe4e215..d11a12fc 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/utils/AsciiDocTestSuite.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/utils/AsciiDocTestSuite.kt @@ -20,9 +20,10 @@ import javax.ws.rs.core.UriBuilder * @param globalMarkers the markers for global blocks */ open class AsciiDocTestSuite( - private val fileName: String, - private val testCaseMarkers: List = emptyList(), - private val globalMarkers: List = listOf(SCHEMA_MARKER)) { + private val fileName: String, + private val testCaseMarkers: List = emptyList(), + private val globalMarkers: List = listOf(SCHEMA_MARKER) +) { private val srcLocation = File("src/test/resources/", fileName).toURI() @@ -49,9 +50,9 @@ open class AsciiDocTestSuite( } class ParsedBlock( - val marker: String, - val uri: URI, - var headline: String? = null + val marker: String, + val uri: URI, + var headline: String? = null ) { var start: Int? = null var end: Int? = null @@ -92,16 +93,19 @@ open class AsciiDocTestSuite( when { !globalDone && globalMarkers.contains(line) -> currentBlock = - startBlock(line, lineNr, globalCodeBlocks) + startBlock(line, lineNr, globalCodeBlocks) + testCaseMarkers.contains(line) -> { globalDone = true currentBlock = startBlock(line, lineNr, codeBlocksOfTest) } + line == "'''" -> { createTests(title, lineNr, ignore) currentBlock = null ignore = false } + line == "----" -> { inside = !inside if (inside) { @@ -116,7 +120,7 @@ open class AsciiDocTestSuite( SCHEMA_MARKER -> { val schemaTests = schemaTestFactory(currentBlock.code()) currentDocumentLevel?.tests?.add(schemaTests) - if (testCaseMarkers.isEmpty()){ + if (testCaseMarkers.isEmpty()) { break@loop } } @@ -143,12 +147,20 @@ open class AsciiDocTestSuite( if (UPDATE_TEST_FILE) { // this test prints out the adjusted test file root?.afterTests?.add( - DynamicTest.dynamicTest("Write updated Testfile", srcLocation, this@AsciiDocTestSuite::writeAdjustedTestFile) + DynamicTest.dynamicTest( + "Write updated Testfile", + srcLocation, + this@AsciiDocTestSuite::writeAdjustedTestFile + ) ) } else if (GENERATE_TEST_FILE_DIFF) { // this test prints out the adjusted test file root?.afterTests?.add( - DynamicTest.dynamicTest("Adjusted Tests", srcLocation, this@AsciiDocTestSuite::printAdjustedTestFile) + DynamicTest.dynamicTest( + "Adjusted Tests", + srcLocation, + this@AsciiDocTestSuite::printAdjustedTestFile + ) ) } return root?.generateTests() ?: Stream.empty() @@ -159,10 +171,11 @@ open class AsciiDocTestSuite( throw IllegalStateException("no code blocks for tests (line $lineNr)") } val tests = testFactory( - title ?: throw IllegalStateException("Title should be defined (line $lineNr)"), - globalCodeBlocks, - codeBlocksOfTest, - ignore) + title ?: throw IllegalStateException("Title should be defined (line $lineNr)"), + globalCodeBlocks, + codeBlocksOfTest, + ignore + ) currentDocumentLevel?.tests?.add(tests) codeBlocksOfTest = mutableMapOf() } @@ -175,9 +188,10 @@ open class AsciiDocTestSuite( val parent = when { depth > currentDepth -> currentDocumentLevel depth == currentDepth -> currentDocumentLevel?.parent - ?: throw IllegalStateException("cannot create sub-level on null") + ?: throw IllegalStateException("cannot create sub-level on null") + else -> currentDocumentLevel?.parent?.parent - ?: throw IllegalStateException("cannot create sub-level on null") + ?: throw IllegalStateException("cannot create sub-level on null") } currentDocumentLevel = DocumentLevel(parent, title, uri) } @@ -197,8 +211,10 @@ open class AsciiDocTestSuite( val rebuildTest = generateAdjustedFileContent() if (!Objects.equals(rebuildTest, fileContent.toString())) { // This special exception will be handled by intellij so that you can diff directly with the file - throw FileComparisonFailure(null, rebuildTest, fileContent.toString(), - null, File("src/test/resources/", fileName).absolutePath) + throw FileComparisonFailure( + null, rebuildTest, fileContent.toString(), + null, File("src/test/resources/", fileName).absolutePath + ) } } @@ -225,7 +241,12 @@ open class AsciiDocTestSuite( return block } - protected open fun testFactory(title: String, globalBlocks: Map>, codeBlocks: Map>, ignore: Boolean): List { + protected open fun testFactory( + title: String, + globalBlocks: Map>, + codeBlocks: Map>, + ignore: Boolean + ): List { return emptyList() } @@ -233,12 +254,16 @@ open class AsciiDocTestSuite( return emptyList() } - protected fun getOrCreateBlocks(codeBlocks: Map>, marker: String, headline: String): List { + protected fun getOrCreateBlocks( + codeBlocks: Map>, + marker: String, + headline: String + ): List { val blocks = codeBlocks[marker]?.toMutableList() ?: mutableListOf() if (blocks.isEmpty() && (GENERATE_TEST_FILE_DIFF || UPDATE_TEST_FILE)) { val insertPoints = testCaseMarkers.indexOf(marker).let { testCaseMarkers.subList(0, it).asReversed() } val insertPoint = insertPoints.mapNotNull { codeBlocks[it]?.firstOrNull() }.firstOrNull() - ?: throw IllegalArgumentException("none of the insert points $insertPoints found in $fileName") + ?: throw IllegalArgumentException("none of the insert points $insertPoints found in $fileName") val block = ParsedBlock(marker, insertPoint.uri, headline) block.start = (insertPoint.end ?: throw IllegalStateException("no start for block defined")) + 6 knownBlocks += blocks @@ -261,9 +286,9 @@ open class AsciiDocTestSuite( const val SCHEMA_CONFIG_MARKER = "[source,json,schema-config=true]" class DocumentLevel( - val parent: DocumentLevel?, - val name: String, - private val testSourceUri: URI + val parent: DocumentLevel?, + val name: String, + private val testSourceUri: URI ) { private val children = mutableListOf() val tests = mutableListOf>() @@ -277,15 +302,26 @@ open class AsciiDocTestSuite( val streamBuilder = Stream.builder() if (tests.size > 1) { if (children.isNotEmpty()) { - streamBuilder.add(DynamicContainer.dynamicContainer(name, testSourceUri, children.stream().flatMap { it.generateTests() })) + streamBuilder.add( + DynamicContainer.dynamicContainer( + name, + testSourceUri, + children.stream().flatMap { it.generateTests() }) + ) } for ((index, test) in tests.withIndex()) { - streamBuilder.add(DynamicContainer.dynamicContainer(name + " " + (index + 1), testSourceUri, test.stream())) + streamBuilder.add( + DynamicContainer.dynamicContainer( + name + " " + (index + 1), + testSourceUri, + test.stream() + ) + ) } } else { val nodes = Stream.concat( - tests.stream().flatMap { it.stream() }, - children.stream().flatMap { it.generateTests() } + tests.stream().flatMap { it.stream() }, + children.stream().flatMap { it.generateTests() } ) streamBuilder.add(DynamicContainer.dynamicContainer(name, testSourceUri, nodes)) } diff --git a/core/src/test/kotlin/org/neo4j/graphql/utils/CypherTestSuite.kt b/core/src/test/kotlin/org/neo4j/graphql/utils/CypherTestSuite.kt index 0d33cd10..8892a77e 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/utils/CypherTestSuite.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/utils/CypherTestSuite.kt @@ -16,28 +16,27 @@ import org.opentest4j.AssertionFailedError import java.util.* import java.util.concurrent.FutureTask import java.util.function.Consumer -import kotlin.streams.toList class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTestSuite( - fileName, - listOf( - SCHEMA_CONFIG_MARKER, - GRAPHQL_MARKER, - GRAPHQL_VARIABLES_MARKER, - GRAPHQL_RESPONSE_MARKER, - GRAPHQL_RESPONSE_IGNORE_ORDER_MARKER, - QUERY_CONFIG_MARKER, - CYPHER_PARAMS_MARKER, - CYPHER_MARKER - ), - listOf(SCHEMA_MARKER, SCHEMA_CONFIG_MARKER, TEST_DATA_MARKER) + fileName, + listOf( + SCHEMA_CONFIG_MARKER, + GRAPHQL_MARKER, + GRAPHQL_VARIABLES_MARKER, + GRAPHQL_RESPONSE_MARKER, + GRAPHQL_RESPONSE_IGNORE_ORDER_MARKER, + QUERY_CONFIG_MARKER, + CYPHER_PARAMS_MARKER, + CYPHER_MARKER + ), + listOf(SCHEMA_MARKER, SCHEMA_CONFIG_MARKER, TEST_DATA_MARKER) ) { override fun testFactory( - title: String, - globalBlocks: Map>, - codeBlocks: Map>, - ignore: Boolean + title: String, + globalBlocks: Map>, + codeBlocks: Map>, + ignore: Boolean ): List { val cypherBlocks = getOrCreateBlocks(codeBlocks, CYPHER_MARKER, "Cypher") @@ -75,37 +74,37 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest } private fun createSchema( - globalBlocks: Map>, - codeBlocks: Map>, - dataFetchingInterceptor: DataFetchingInterceptor? = null + globalBlocks: Map>, + codeBlocks: Map>, + dataFetchingInterceptor: DataFetchingInterceptor? = null ): GraphQLSchema { val schemaString = globalBlocks[SCHEMA_MARKER]?.firstOrNull()?.code() - ?: throw IllegalStateException("Schema should be defined") + ?: throw IllegalStateException("Schema should be defined") val schemaConfig = (codeBlocks[SCHEMA_CONFIG_MARKER]?.firstOrNull() - ?: globalBlocks[SCHEMA_CONFIG_MARKER]?.firstOrNull())?.code() + ?: globalBlocks[SCHEMA_CONFIG_MARKER]?.firstOrNull())?.code() ?.let { return@let MAPPER.readValue(it, SchemaConfig::class.java) } - ?: SchemaConfig() + ?: SchemaConfig() return SchemaBuilder.buildSchema(schemaString, schemaConfig, dataFetchingInterceptor) } private fun createTransformationTask( - title: String, - globalBlocks: Map>, - codeBlocks: Map> + title: String, + globalBlocks: Map>, + codeBlocks: Map> ): () -> List { val transformationTask = FutureTask { val schema = createSchema(globalBlocks, codeBlocks) val request = codeBlocks[GRAPHQL_MARKER]?.firstOrNull()?.code() - ?: throw IllegalStateException("missing graphql for $title") + ?: throw IllegalStateException("missing graphql for $title") val requestParams = codeBlocks[GRAPHQL_VARIABLES_MARKER]?.firstOrNull()?.code()?.parseJsonMap() - ?: emptyMap() + ?: emptyMap() val queryContext = codeBlocks[QUERY_CONFIG_MARKER]?.firstOrNull()?.code() ?.let { config -> return@let MAPPER.readValue(config, QueryContext::class.java) } - ?: QueryContext() + ?: QueryContext() Translator(schema) .translate(request, requestParams, queryContext) @@ -116,25 +115,31 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest } } - private fun printGeneratedQuery(result: () -> List): DynamicTest = DynamicTest.dynamicTest("Generated query") { - result().forEach { println(it) } - } + private fun printGeneratedQuery(result: () -> List): DynamicTest = + DynamicTest.dynamicTest("Generated query") { + result().forEach { println(it) } + } - private fun printReplacedParameter(result: () -> List): DynamicTest = DynamicTest.dynamicTest("Generated query with params replaced") { - result().forEach { - var queryWithReplacedParams = it.query - it.params.forEach { (key, value) -> - queryWithReplacedParams = + private fun printReplacedParameter(result: () -> List): DynamicTest = + DynamicTest.dynamicTest("Generated query with params replaced") { + result().forEach { + var queryWithReplacedParams = it.query + it.params.forEach { (key, value) -> + queryWithReplacedParams = queryWithReplacedParams.replace("$$key", if (value is String) "'$value'" else value.toString()) + } + println() + println("Generated query with params replaced") + println("------------------------------------") + println(queryWithReplacedParams) } - println() - println("Generated query with params replaced") - println("------------------------------------") - println(queryWithReplacedParams) } - } - private fun testCypher(title: String, cypherBlocks: List, result: () -> List): List = cypherBlocks.mapIndexed { index, cypherBlock -> + private fun testCypher( + title: String, + cypherBlocks: List, + result: () -> List + ): List = cypherBlocks.mapIndexed { index, cypherBlock -> var name = "Test Cypher" if (cypherBlocks.size > 1) { name += " (${index + 1})" @@ -143,7 +148,7 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest val cypher = cypherBlock.code() val expected = cypher.normalize() val actual = (result().getOrNull(index)?.query - ?: throw IllegalStateException("missing cypher query for $title ($index)")) + ?: throw IllegalStateException("missing cypher query for $title ($index)")) val actualNormalized = actual.normalize() if (!Objects.equals(expected, actual)) { @@ -155,7 +160,10 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest } } - private fun testCypherParams(codeBlocks: Map>, result: () -> List): List { + private fun testCypherParams( + codeBlocks: Map>, + result: () -> List + ): List { val cypherParamsBlocks = getOrCreateBlocks(codeBlocks, CYPHER_PARAMS_MARKER, "Cypher Params") return cypherParamsBlocks.mapIndexed { index, cypherParamsBlock -> @@ -165,7 +173,7 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest } DynamicTest.dynamicTest(name, cypherParamsBlock.uri) { val resultParams = result().getOrNull(index)?.params - ?: throw IllegalStateException("Expected a cypher query with index $index") + ?: throw IllegalStateException("Expected a cypher query with index $index") val actualParams = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(resultParams) if (cypherParamsBlock.code().isBlank()) { @@ -212,32 +220,34 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest } private fun integrationTest( - title: String, - globalBlocks: Map>, - codeBlocks: Map>, - testData: ParsedBlock, - response: ParsedBlock, - ignoreOrder: Boolean + title: String, + globalBlocks: Map>, + codeBlocks: Map>, + testData: ParsedBlock, + response: ParsedBlock, + ignoreOrder: Boolean ): DynamicNode = DynamicTest.dynamicTest("Integration Test", response.uri) { val dataFetchingInterceptor = setupDataFetchingInterceptor(testData) val request = codeBlocks[GRAPHQL_MARKER]?.firstOrNull()?.code() - ?: throw IllegalStateException("missing graphql for $title") + ?: throw IllegalStateException("missing graphql for $title") val requestParams = codeBlocks[GRAPHQL_VARIABLES_MARKER]?.firstOrNull()?.code()?.parseJsonMap() ?: emptyMap() val queryContext = codeBlocks[QUERY_CONFIG_MARKER]?.firstOrNull()?.code() ?.let { config -> return@let MAPPER.readValue(config, QueryContext::class.java) } - ?: QueryContext() + ?: QueryContext() val schema = createSchema(globalBlocks, codeBlocks, dataFetchingInterceptor) val graphql = GraphQL.newGraphQL(schema).build() - val result = graphql.execute(ExecutionInput.newExecutionInput() - .query(request) - .variables(requestParams) - .context(queryContext) - .build()) + val result = graphql.execute( + ExecutionInput.newExecutionInput() + .query(request) + .variables(requestParams) + .context(queryContext) + .build() + ) Assertions.assertThat(result.errors).isEmpty() val values = result?.getData() @@ -266,12 +276,15 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest .hasSize(expected.size) .containsOnlyKeys(*expected.keys.toTypedArray()) .satisfies(Consumer { it.forEach { (key, value) -> assertEqualIgnoreOrder(expected[key], value) } }) + is Collection<*> -> { - val assertions: List> = expected.map { e -> Consumer { a -> assertEqualIgnoreOrder(e, a) } } + val assertions: List> = + expected.map { e -> Consumer { a -> assertEqualIgnoreOrder(e, a) } } Assertions.assertThat(actual).asInstanceOf(InstanceOfAssertFactories.LIST) .hasSize(expected.size) .satisfiesExactlyInAnyOrder(*assertions.toTypedArray()) } + else -> Assertions.assertThat(actual).isEqualTo(expected) } } diff --git a/core/src/test/kotlin/org/neo4j/graphql/utils/GraphQLSchemaTestSuite.kt b/core/src/test/kotlin/org/neo4j/graphql/utils/GraphQLSchemaTestSuite.kt index 2cd44847..9cc9f0a8 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/utils/GraphQLSchemaTestSuite.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/utils/GraphQLSchemaTestSuite.kt @@ -22,24 +22,29 @@ import java.util.* import java.util.regex.Pattern class GraphQLSchemaTestSuite(fileName: String) : AsciiDocTestSuite( - fileName, - listOf(SCHEMA_CONFIG_MARKER, GRAPHQL_MARKER) + fileName, + listOf(SCHEMA_CONFIG_MARKER, GRAPHQL_MARKER) ) { - override fun testFactory(title: String, globalBlocks: Map>, codeBlocks: Map>, ignore: Boolean): List { + override fun testFactory( + title: String, + globalBlocks: Map>, + codeBlocks: Map>, + ignore: Boolean + ): List { val targetSchemaBlock = codeBlocks[GRAPHQL_MARKER]?.first() val compareSchemaTest = DynamicTest.dynamicTest("compare schema", targetSchemaBlock?.uri) { val configBlock = codeBlocks[SCHEMA_CONFIG_MARKER]?.first() val config = configBlock?.code()?.let { MAPPER.readValue(it, SchemaConfig::class.java) } ?: SchemaConfig() val targetSchema = targetSchemaBlock?.code() - ?: throw IllegalStateException("missing graphql for $title") + ?: throw IllegalStateException("missing graphql for $title") var augmentedSchema: GraphQLSchema? = null var expectedSchema: GraphQLSchema? = null try { val schema = globalBlocks[SCHEMA_MARKER]?.first()?.code() - ?: throw IllegalStateException("Schema should be defined") + ?: throw IllegalStateException("Schema should be defined") augmentedSchema = SchemaBuilder.buildSchema(schema, config) val schemaParser = SchemaParser() @@ -53,11 +58,12 @@ class GraphQLSchemaTestSuite(fileName: String) : AsciiDocTestSuite( .scalars() .filterNot { entry -> ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.containsKey(entry.key) } .forEach { (name, definition) -> - runtimeWiring.scalar(GraphQLScalarType.newScalar() - .name(name) - .definition(definition) - .coercing(NoOpCoercing) - .build() + runtimeWiring.scalar( + GraphQLScalarType.newScalar() + .name(name) + .definition(definition) + .coercing(NoOpCoercing) + .build() ) } expectedSchema = schemaGenerator.makeExecutableSchema(reg, runtimeWiring.build()) @@ -75,9 +81,9 @@ class GraphQLSchemaTestSuite(fileName: String) : AsciiDocTestSuite( val actualSchema = SCHEMA_PRINTER.print(augmentedSchema) targetSchemaBlock.adjustedCode = actualSchema throw AssertionFailedError("augmented schema differs for '$title'", - expectedSchema?.let { SCHEMA_PRINTER.print(it) } ?: targetSchema, - actualSchema, - e) + expectedSchema?.let { SCHEMA_PRINTER.print(it) } ?: targetSchema, + actualSchema, + e) } } @@ -89,11 +95,12 @@ class GraphQLSchemaTestSuite(fileName: String) : AsciiDocTestSuite( private const val GRAPHQL_MARKER = "[source,graphql]" private val METHOD_PATTERN = Pattern.compile("(add|delete|update|merge|create)(.*)") - private val SCHEMA_PRINTER = SchemaPrinter(SchemaPrinter.Options.defaultOptions() - .includeDirectives(false) - .includeScalarTypes(true) - .includeSchemaDefinition(true) - .includeIntrospectionTypes(false) + private val SCHEMA_PRINTER = SchemaPrinter( + SchemaPrinter.Options.defaultOptions() + .includeDirectives(false) + .includeScalarTypes(true) + .includeSchemaDefinition(true) + .includeIntrospectionTypes(false) ) fun GraphQLType.splitName(): Pair { diff --git a/core/src/test/kotlin/org/neo4j/graphql/utils/TestUtils.kt b/core/src/test/kotlin/org/neo4j/graphql/utils/TestUtils.kt index b52931d0..37abf222 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/utils/TestUtils.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/utils/TestUtils.kt @@ -8,15 +8,16 @@ import java.util.stream.Stream object TestUtils { - fun createTestsInPath(path: String, factory: (testFile: String) -> Stream): Stream? = Files - .list(Paths.get("src/test/resources/$path")) - .sorted() - .map { - val tests = if (Files.isDirectory(it)) { - createTestsInPath("$path/${it.fileName}", factory) - } else { - factory("$path/${it.fileName}") + fun createTestsInPath(path: String, factory: (testFile: String) -> Stream): Stream? = + Files + .list(Paths.get("src/test/resources/$path")) + .sorted() + .map { + val tests = if (Files.isDirectory(it)) { + createTestsInPath("$path/${it.fileName}", factory) + } else { + factory("$path/${it.fileName}") + } + DynamicContainer.dynamicContainer(it.fileName.toString(), it.toUri(), tests) } - DynamicContainer.dynamicContainer(it.fileName.toString(), it.toUri(), tests) - } } diff --git a/core/src/test/resources/cypher-directive-tests.adoc b/core/src/test/resources/cypher-directive-tests.adoc index 1dfbe637..f35f4963 100644 --- a/core/src/test/resources/cypher-directive-tests.adoc +++ b/core/src/test/resources/cypher-directive-tests.adoc @@ -415,7 +415,6 @@ RETURN user AS user ''' - === pass through directives result in field .GraphQL-Query diff --git a/core/src/test/resources/cypher-tests.adoc b/core/src/test/resources/cypher-tests.adoc index ac57dfdb..969d5693 100644 --- a/core/src/test/resources/cypher-tests.adoc +++ b/core/src/test/resources/cypher-tests.adoc @@ -308,7 +308,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie year } } - + ---- .Cypher params @@ -350,7 +350,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` { .title ,actors: [(`movie`)<-[:`ACTED_IN`]-(`movie_actors`:`Actor`) | movie_actors { .name }] ,similar: [ movie_similar IN apoc.cypher.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie, cypherParams: $cypherParams, offset: 0, first: $1_first}, true) | movie_similar { .title }][..3] } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` { .title ,actors: [(`movie`)<-[:`ACTED_IN`]-(`movie_actors`:`Actor`) | movie_actors { .name }] ,similar: [ movie_similar IN apoc.CYPHER.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie, cypherParams: $cypherParams, offset: 0, first: $1_first}, true) | movie_similar { .title }][..3] } AS `movie` ---- === Handle Query with name not aligning to type @@ -363,7 +363,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie title } } - + ---- .Cypher params @@ -441,7 +441,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {movieId:$movieId}) RETURN `m } } } - + ---- .Cypher params @@ -526,7 +526,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {movieId:$movieId}) RETURN `m .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` { .title ,actors: [(`movie`)<-[:`ACTED_IN`]-(`movie_actors`:`Actor`) | movie_actors { .name ,movies: [(`movie_actors`)-[:`ACTED_IN`]->(`movie_actors_movies`:`Movie`:`u_user-id`:`newMovieLabel`) | movie_actors_movies { .title ,actors: [(`movie_actors_movies`)<-[:`ACTED_IN`]-(`movie_actors_movies_actors`:`Actor`{name:$1_name}) | movie_actors_movies_actors { .name ,movies: [(`movie_actors_movies_actors`)-[:`ACTED_IN`]->(`movie_actors_movies_actors_movies`:`Movie`:`u_user-id`:`newMovieLabel`) | movie_actors_movies_actors_movies { .title , .year ,similar: [ movie_actors_movies_actors_movies_similar IN apoc.cypher.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie_actors_movies_actors_movies, cypherParams: $cypherParams, offset: 0, first: $2_first}, true) | movie_actors_movies_actors_movies_similar { .title , .year }][..3] }] }] }] }] } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` { .title ,actors: [(`movie`)<-[:`ACTED_IN`]-(`movie_actors`:`Actor`) | movie_actors { .name ,movies: [(`movie_actors`)-[:`ACTED_IN`]->(`movie_actors_movies`:`Movie`:`u_user-id`:`newMovieLabel`) | movie_actors_movies { .title ,actors: [(`movie_actors_movies`)<-[:`ACTED_IN`]-(`movie_actors_movies_actors`:`Actor`{name:$1_name}) | movie_actors_movies_actors { .name ,movies: [(`movie_actors_movies_actors`)-[:`ACTED_IN`]->(`movie_actors_movies_actors_movies`:`Movie`:`u_user-id`:`newMovieLabel`) | movie_actors_movies_actors_movies { .title , .year ,similar: [ movie_actors_movies_actors_movies_similar IN apoc.CYPHER.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie_actors_movies_actors_movies, cypherParams: $cypherParams, offset: 0, first: $2_first}, true) | movie_actors_movies_actors_movies_similar { .title , .year }][..3] }] }] }] }] } AS `movie` ---- === Handle meta field at beginning of selection set @@ -567,7 +567,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie __typename } } - + ---- .Cypher params @@ -595,7 +595,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie year } } - + ---- .Cypher params @@ -635,7 +635,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` {mostSimilar: head([ movie_mostSimilar IN apoc.cypher.runFirstColumn("WITH {this} AS this RETURN this", {this: movie, cypherParams: $cypherParams}, true) | movie_mostSimilar { .title , .year }]) } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` {mostSimilar: head([ movie_mostSimilar IN apoc.cypher.runFirstColumn('WITH {this} AS this RETURN this', {this: movie, cypherParams: $cypherParams}, true) | movie_mostSimilar { .title , .year }]) } AS `movie` ---- === Pass @cypher directive default params to sub-query @@ -660,7 +660,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` {scaleRating: apoc.cypher.runFirstColumn("WITH $this AS this RETURN $scale * this.imdbRating", {this: movie, cypherParams: $cypherParams, scale: 3}, false)} AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` {scaleRating: apoc.cypher.runFirstColumn('WITH $this AS this RETURN $scale * this.imdbRating', {this: movie, cypherParams: $cypherParams, scale: 3}, false)} AS `movie` ---- === Pass @cypher directive params to sub-query @@ -685,7 +685,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` {scaleRating: apoc.cypher.runFirstColumn("WITH $this AS this RETURN $scale * this.imdbRating", {this: movie, cypherParams: $cypherParams, scale: $1_scale}, false)} AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` {scaleRating: apoc.CYPHER.runFirstColumn("WITH $this AS this RETURN $scale * this.imdbRating", {this: movie, cypherParams: $cypherParams, scale: $1_scale}, false)} AS `movie` ---- === Query for Neo4js internal _id @@ -711,7 +711,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel`) WHERE ID(`movie`)=0 RETURN `movie` { .title , .year } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel`) WHERE id(`movie`)=0 RETURN `movie` { .title , .year } AS `movie` ---- === Query for Neo4js internal _id and another param before _id @@ -737,7 +737,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel`) WHERE ID(`movie`)=0 RETURN ` .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) WHERE ID(`movie`)=0 RETURN `movie` { .title , .year } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) WHERE id(`movie`)=0 RETURN `movie` { .title , .year } AS `movie` ---- === Query for Neo4js internal _id and another param after _id @@ -763,7 +763,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) WHERE ID(`mov .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) WHERE ID(`movie`)=0 RETURN `movie` { .title , .year } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) WHERE id(`movie`)=0 RETURN `movie` { .title , .year } AS `movie` ---- === Query for Neo4js internal _id by dedicated Query MovieBy_Id(_id: String!) @@ -789,7 +789,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) WHERE ID(`movie .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel`) WHERE ID(`movie`)=0 RETURN `movie` { .title , .year } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel`) WHERE id(`movie`)=0 RETURN `movie` { .title , .year } AS `movie` ---- === Query for null value translates to 'IS NULL' WHERE clause @@ -839,7 +839,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel`) WHERE movie.poster IS NULL R .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) WHERE ID(`movie`)=0 AND movie.poster IS NULL RETURN `movie` { .title , .year } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) WHERE id(`movie`)=0 AND movie.poster IS NULL RETURN `movie` { .title , .year } AS `movie` ---- === Cypher subquery filters @@ -870,7 +870,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) WHERE ID(`movie .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` { .title ,actors: [(`movie`)<-[:`ACTED_IN`]-(`movie_actors`:`Actor`{name:$1_name}) | movie_actors { .name }] ,similar: [ movie_similar IN apoc.cypher.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie, cypherParams: $cypherParams, offset: 0, first: $3_first}, true) | movie_similar { .title }][..3] } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie` { .title ,actors: [(`movie`)<-[:`ACTED_IN`]-(`movie_actors`:`Actor`{name:$1_name}) | movie_actors { .name }] ,similar: [ movie_similar IN apoc.CYPHER.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie, cypherParams: $cypherParams, offset: 0, first: $3_first}, true) | movie_similar { .title }][..3] } AS `movie` ---- === Cypher subquery filters with paging @@ -901,7 +901,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`user-id`:`newMovieLabel` {title:$title}) RETURN `movie` { .title ,actors: [(`movie`)<-[:`ACTED_IN`]-(`movie_actors`:`Actor`{name:$1_name}) | movie_actors { .name }][..3] ,similar: [ movie_similar IN apoc.cypher.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie, cypherParams: $cypherParams, offset: 0, first: $3_first}, true) | movie_similar { .title }][..3] } AS `movie` +MATCH (`movie`:`Movie`:`user-id`:`newMovieLabel` {title:$title}) RETURN `movie` { .title ,actors: [(`movie`)<-[:`ACTED_IN`]-(`movie_actors`:`Actor`{name:$1_name}) | movie_actors { .name }][..3] ,similar: [ movie_similar IN apoc.CYPHER.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie, cypherParams: $cypherParams, offset: 0, first: $3_first}, true) | movie_similar { .title }][..3] } AS `movie` ---- === Handle @cypher directive on Query Type @@ -918,7 +918,7 @@ MATCH (`movie`:`Movie`:`user-id`:`newMovieLabel` {title:$title}) RETURN `movie` } } } - + ---- .Cypher params @@ -930,7 +930,7 @@ MATCH (`movie`:`Movie`:`user-id`:`newMovieLabel` {title:$title}) RETURN `movie` .Cypher [source,cypher] ---- -WITH apoc.cypher.runFirstColumn("MATCH (g:Genre) WHERE toLower(g.name) CONTAINS toLower($substring) RETURN g", {offset:$offset, first:$first, substring:$substring, cypherParams: $cypherParams}, True) AS x UNWIND x AS `genre` RETURN `genre` { .name ,movies: [(`genre`)<-[:`IN_GENRE`]-(`genre_movies`:`Movie`:`u_user-id`:`newMovieLabel`) | genre_movies { .title }][..3] } AS `genre` +WITH apoc.cypher.runFirstColumn('MATCH (g:Genre) WHERE toLower(g.name) CONTAINS toLower($substring) RETURN g', {offset:$offset, first:$first, substring:$substring, cypherParams: $cypherParams}, true) AS x UNWIND x AS `genre` RETURN `genre` { .name ,movies: [(`genre`)<-[:`IN_GENRE`]-(`genre_movies`:`Movie`:`u_user-id`:`newMovieLabel`) | genre_movies { .title }][..3] } AS `genre` ---- === Add relationship mutation @@ -970,8 +970,8 @@ mutation someMutation { MATCH (`movie_from`:`Movie`:`u_user-id`:`newMovieLabel` {movieId: $from.movieId}) MATCH (`genre_to`:`Genre` {name: $to.name}) CREATE (`movie_from`)-[`in_genre_relation`:`IN_GENRE`]->(`genre_to`) - RETURN `in_genre_relation` { from: `movie_from` { .movieId ,genres: [(`movie_from`)-[:`IN_GENRE`]->(`movie_from_genres`:`Genre`) | movie_from_genres {_id: ID(`movie_from_genres`), .name }] } ,to: `genre_to` { .name } } AS `_AddMovieGenresPayload`; - + RETURN `in_genre_relation` { FROM: `movie_from` { .movieId ,genres: [(`movie_from`)-[:`IN_GENRE`]->(`movie_from_genres`:`Genre`) | movie_from_genres {_id: ID(`movie_from_genres`), .name }] } ,to: `genre_to` { .name } } AS `_AddMovieGenresPayload`; + ---- === Add relationship mutation with GraphQL variables @@ -1011,8 +1011,8 @@ mutation someMutation($from: _MovieInput!) { MATCH (`movie_from`:`Movie`:`u_user-id`:`newMovieLabel` {movieId: $from.movieId}) MATCH (`genre_to`:`Genre` {name: $to.name}) CREATE (`movie_from`)-[`in_genre_relation`:`IN_GENRE`]->(`genre_to`) - RETURN `in_genre_relation` { from: `movie_from` { .movieId ,genres: [(`movie_from`)-[:`IN_GENRE`]->(`movie_from_genres`:`Genre`) | movie_from_genres {_id: ID(`movie_from_genres`), .name }] } ,to: `genre_to` { .name } } AS `_AddMovieGenresPayload`; - + RETURN `in_genre_relation` { FROM: `movie_from` { .movieId ,genres: [(`movie_from`)-[:`IN_GENRE`]->(`movie_from_genres`:`Genre`) | movie_from_genres {_id: ID(`movie_from_genres`), .name }] } ,to: `genre_to` { .name } } AS `_AddMovieGenresPayload`; + ---- === Add relationship mutation with relationship property @@ -1048,7 +1048,7 @@ mutation someMutation { to { _id movieId - title + title ratings { rating User { @@ -1076,8 +1076,8 @@ mutation someMutation { MATCH (`user_from`:`User` {userId: $from.userId}) MATCH (`movie_to`:`Movie`:`u_user-id`:`newMovieLabel` {movieId: $to.movieId}) CREATE (`user_from`)-[`rated_relation`:`RATED` {rating:$data.rating}]->(`movie_to`) - RETURN `rated_relation` { from: `user_from` {_id: ID(`user_from`), .userId , .name ,rated: [(`user_from`)-[`user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_relation { .rating ,Movie: head([(:`User`)-[`user_from_rated_relation`]->(`user_from_rated_Movie`:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_Movie {_id: ID(`user_from_rated_Movie`), .movieId , .title }]) }] } ,to: `movie_to` {_id: ID(`movie_to`), .movieId , .title ,ratings: [(`movie_to`)<-[`movie_to_ratings_relation`:`RATED`]-(:`User`) | movie_to_ratings_relation { .rating ,User: head([(:`Movie`:`u_user-id`:`newMovieLabel`)<-[`movie_to_ratings_relation`]-(`movie_to_ratings_User`:`User`) | movie_to_ratings_User {_id: ID(`movie_to_ratings_User`), .userId , .name }]) }] } , .rating } AS `_AddUserRatedPayload`; - + RETURN `rated_relation` { FROM: `user_from` {_id: ID(`user_from`), .userId , .name ,rated: [(`user_from`)-[`user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_relation { .rating ,Movie: head([(:`User`)-[`user_from_rated_relation`]->(`user_from_rated_Movie`:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_Movie {_id: ID(`user_from_rated_Movie`), .movieId , .title }]) }] } ,to: `movie_to` {_id: ID(`movie_to`), .movieId , .title ,ratings: [(`movie_to`)<-[`movie_to_ratings_relation`:`RATED`]-(:`User`) | movie_to_ratings_relation { .rating ,User: head([(:`Movie`:`u_user-id`:`newMovieLabel`)<-[`movie_to_ratings_relation`]-(`movie_to_ratings_User`:`User`) | movie_to_ratings_User {_id: ID(`movie_to_ratings_User`), .userId , .name }]) }] } , .rating } AS `_AddUserRatedPayload`; + ---- === Add reflexive relationship mutation with relationship property @@ -1157,7 +1157,7 @@ mutation { since } } - + ---- .Cypher params @@ -1173,8 +1173,8 @@ mutation { MATCH (`user_from`:`User` {userId: $from.userId}) MATCH (`user_to`:`User` {userId: $to.userId}) CREATE (`user_from`)-[`friend_of_relation`:`FRIEND_OF` {since:$data.since}]->(`user_to`) - RETURN `friend_of_relation` { from: `user_from` {_id: ID(`user_from`), .userId , .name ,friends: {from: [(`user_from`)<-[`user_from_from_relation`:`FRIEND_OF`]-(`user_from_from`:`User`) | user_from_from_relation { .since ,User: user_from_from {_id: ID(`user_from_from`), .name ,friends: {from: [(`user_from_from`)<-[`user_from_from_from_relation`:`FRIEND_OF`]-(`user_from_from_from`:`User`) | user_from_from_from_relation { .since ,User: user_from_from_from {_id: ID(`user_from_from_from`), .name } }] ,to: [(`user_from_from`)-[`user_from_from_to_relation`:`FRIEND_OF`]->(`user_from_from_to`:`User`) | user_from_from_to_relation { .since ,User: user_from_from_to {_id: ID(`user_from_from_to`), .name } }] } } }] ,to: [(`user_from`)-[`user_from_to_relation`:`FRIEND_OF`]->(`user_from_to`:`User`) | user_from_to_relation { .since ,User: user_from_to {_id: ID(`user_from_to`), .name } }] } } ,to: `user_to` {_id: ID(`user_to`), .name ,friends: {from: [(`user_to`)<-[`user_to_from_relation`:`FRIEND_OF`]-(`user_to_from`:`User`) | user_to_from_relation { .since ,User: user_to_from {_id: ID(`user_to_from`), .name } }] ,to: [(`user_to`)-[`user_to_to_relation`:`FRIEND_OF`]->(`user_to_to`:`User`) | user_to_to_relation { .since ,User: user_to_to {_id: ID(`user_to_to`), .name } }] } } , .since } AS `_AddUserFriendsPayload`; - + RETURN `friend_of_relation` { FROM: `user_from` {_id: ID(`user_from`), .userId , .name ,friends: {FROM: [(`user_from`)<-[`user_from_from_relation`:`FRIEND_OF`]-(`user_from_from`:`User`) | user_from_from_relation { .since ,User: user_from_from {_id: ID(`user_from_from`), .name ,friends: {FROM: [(`user_from_from`)<-[`user_from_from_from_relation`:`FRIEND_OF`]-(`user_from_from_from`:`User`) | user_from_from_from_relation { .since ,User: user_from_from_from {_id: ID(`user_from_from_from`), .name } }] ,to: [(`user_from_from`)-[`user_from_from_to_relation`:`FRIEND_OF`]->(`user_from_from_to`:`User`) | user_from_from_to_relation { .since ,User: user_from_from_to {_id: ID(`user_from_from_to`), .name } }] } } }] ,to: [(`user_from`)-[`user_from_to_relation`:`FRIEND_OF`]->(`user_from_to`:`User`) | user_from_to_relation { .since ,User: user_from_to {_id: ID(`user_from_to`), .name } }] } } ,to: `user_to` {_id: ID(`user_to`), .name ,friends: {FROM: [(`user_to`)<-[`user_to_from_relation`:`FRIEND_OF`]-(`user_to_from`:`User`) | user_to_from_relation { .since ,User: user_to_from {_id: ID(`user_to_from`), .name } }] ,to: [(`user_to`)-[`user_to_to_relation`:`FRIEND_OF`]->(`user_to_to`:`User`) | user_to_to_relation { .since ,User: user_to_to {_id: ID(`user_to_to`), .name } }] } } , .since } AS `_AddUserFriendsPayload`; + ---- === Remove relationship mutation @@ -1214,8 +1214,8 @@ mutation someMutation { OPTIONAL MATCH (`movie_from`)-[`movie_fromgenre_to`:`IN_GENRE`]->(`genre_to`) DELETE `movie_fromgenre_to` WITH COUNT(*) AS scope, `movie_from` AS `_movie_from`, `genre_to` AS `_genre_to` - RETURN {from: `_movie_from` {_id: ID(`_movie_from`), .title } ,to: `_genre_to` {_id: ID(`_genre_to`), .name } } AS `_RemoveMovieGenresPayload`; - + RETURN {FROM: `_movie_from` {_id: ID(`_movie_from`), .title } ,to: `_genre_to` {_id: ID(`_genre_to`), .name } } AS `_RemoveMovieGenresPayload`; + ---- === Remove reflexive relationship mutation @@ -1270,11 +1270,11 @@ mutation { name } } - } + } } } } - + ---- .Cypher params @@ -1292,8 +1292,8 @@ mutation { OPTIONAL MATCH (`user_from`)-[`user_fromuser_to`:`FRIEND_OF`]->(`user_to`) DELETE `user_fromuser_to` WITH COUNT(*) AS scope, `user_from` AS `_user_from`, `user_to` AS `_user_to` - RETURN {from: `_user_from` {_id: ID(`_user_from`), .name ,friends: {from: [(`_user_from`)<-[`_user_from_from_relation`:`FRIEND_OF`]-(`_user_from_from`:`User`) | _user_from_from_relation { .since ,User: _user_from_from {_id: ID(`_user_from_from`), .name } }] ,to: [(`_user_from`)-[`_user_from_to_relation`:`FRIEND_OF`]->(`_user_from_to`:`User`) | _user_from_to_relation { .since ,User: _user_from_to {_id: ID(`_user_from_to`), .name } }] } } ,to: `_user_to` {_id: ID(`_user_to`), .name ,friends: {from: [(`_user_to`)<-[`_user_to_from_relation`:`FRIEND_OF`]-(`_user_to_from`:`User`) | _user_to_from_relation { .since ,User: _user_to_from {_id: ID(`_user_to_from`), .name } }] ,to: [(`_user_to`)-[`_user_to_to_relation`:`FRIEND_OF`]->(`_user_to_to`:`User`) | _user_to_to_relation { .since ,User: _user_to_to {_id: ID(`_user_to_to`), .name } }] } } } AS `_RemoveUserFriendsPayload`; - + RETURN {FROM: `_user_from` {_id: ID(`_user_from`), .name ,friends: {FROM: [(`_user_from`)<-[`_user_from_from_relation`:`FRIEND_OF`]-(`_user_from_from`:`User`) | _user_from_from_relation { .since ,User: _user_from_from {_id: ID(`_user_from_from`), .name } }] ,to: [(`_user_from`)-[`_user_from_to_relation`:`FRIEND_OF`]->(`_user_from_to`:`User`) | _user_from_to_relation { .since ,User: _user_from_to {_id: ID(`_user_from_to`), .name } }] } } ,to: `_user_to` {_id: ID(`_user_to`), .name ,friends: {FROM: [(`_user_to`)<-[`_user_to_from_relation`:`FRIEND_OF`]-(`_user_to_from`:`User`) | _user_to_from_relation { .since ,User: _user_to_from {_id: ID(`_user_to_from`), .name } }] ,to: [(`_user_to`)-[`_user_to_to_relation`:`FRIEND_OF`]->(`_user_to_to`:`User`) | _user_to_to_relation { .since ,User: _user_to_to {_id: ID(`_user_to_to`), .name } }] } } } AS `_RemoveUserFriendsPayload`; + ---- === Handle GraphQL variables in nested selection - first/offset @@ -1322,7 +1322,7 @@ query ($year: Int!, $first: Int!) { .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) RETURN `movie` { .title , .year ,similar: [ movie_similar IN apoc.cypher.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie, cypherParams: $cypherParams, offset: 0, first: $1_first}, true) | movie_similar { .title }][..3] } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) RETURN `movie` { .title , .year ,similar: [ movie_similar IN apoc.CYPHER.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie, cypherParams: $cypherParams, offset: 0, first: $1_first}, true) | movie_similar { .title }][..3] } AS `movie` ---- === Handle GraphQL variables in nest selection - @cypher param (not first/offset) @@ -1353,7 +1353,7 @@ query ($year: Int = 2016, $first: Int = 2, $scale:Int) { .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) RETURN `movie` { .title , .year ,similar: [ movie_similar IN apoc.cypher.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie, cypherParams: $cypherParams, offset: 0, first: $1_first}, true) | movie_similar { .title ,scaleRating: apoc.cypher.runFirstColumn("WITH $this AS this RETURN $scale * this.imdbRating", {this: movie_similar, cypherParams: $cypherParams, scale: $2_scale}, false)}][..3] } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) RETURN `movie` { .title , .year ,similar: [ movie_similar IN apoc.CYPHER.runFirstColumn("WITH {this} AS this MATCH (this)--(:Genre)--(o:Movie) RETURN o", {this: movie, cypherParams: $cypherParams, offset: 0, first: $1_first}, true) | movie_similar { .title ,scaleRating: apoc.CYPHER.runFirstColumn("WITH $this AS this RETURN $scale * this.imdbRating", {this: movie_similar, cypherParams: $cypherParams, scale: $2_scale}, false)}][..3] } AS `movie` ---- === Return internal node id for _id field @@ -1468,7 +1468,7 @@ query getMovie { year } } - + ---- .Cypher params @@ -1494,12 +1494,12 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {title:$title}) RETURN `movie ...Foo } } - + fragment Foo on Movie { title ...Bar } - + fragment Bar on Movie { year } @@ -1531,7 +1531,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) RETURN `movie` } } } - + fragment Foo on Actor { name } @@ -1560,14 +1560,14 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) RETURN `movie` ...Foo } } - + fragment Foo on Movie { title actors { ...Bar } } - + fragment Bar on Actor { name } @@ -1598,7 +1598,7 @@ MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {year:$year}) RETURN `movie` } } } - + ---- .Cypher params @@ -1737,7 +1737,7 @@ query { Movie { _id ratings { - rating + rating User { _id friends { @@ -1748,7 +1748,7 @@ query { } } to { - since + since User { _id } @@ -1787,7 +1787,7 @@ query { .Cypher [source,cypher] ---- -MATCH (`user`:`User`) RETURN `user` {_id: ID(`user`), .name ,friends: {from: [(`user`)<-[`user_from_relation`:`FRIEND_OF`]-(`user_from`:`User`) | user_from_relation { .since ,User: user_from {_id: ID(`user_from`), .name ,rated: [(`user_from`)-[`user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_relation { .rating ,Movie: head([(:`User`)-[`user_from_rated_relation`]->(`user_from_rated_Movie`:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_Movie {_id: ID(`user_from_rated_Movie`),ratings: [(`user_from_rated_Movie`)<-[`user_from_rated_Movie_ratings_relation`:`RATED`]-(:`User`) | user_from_rated_Movie_ratings_relation { .rating ,User: head([(:`Movie`:`u_user-id`:`newMovieLabel`)<-[`user_from_rated_Movie_ratings_relation`]-(`user_from_rated_Movie_ratings_User`:`User`) | user_from_rated_Movie_ratings_User {_id: ID(`user_from_rated_Movie_ratings_User`),friends: {from: [(`user_from_rated_Movie_ratings_User`)<-[`user_from_rated_Movie_ratings_User_from_relation`:`FRIEND_OF`]-(`user_from_rated_Movie_ratings_User_from`:`User`) | user_from_rated_Movie_ratings_User_from_relation { .since ,User: user_from_rated_Movie_ratings_User_from {_id: ID(`user_from_rated_Movie_ratings_User_from`)} }] ,to: [(`user_from_rated_Movie_ratings_User`)-[`user_from_rated_Movie_ratings_User_to_relation`:`FRIEND_OF`]->(`user_from_rated_Movie_ratings_User_to`:`User`) | user_from_rated_Movie_ratings_User_to_relation { .since ,User: user_from_rated_Movie_ratings_User_to {_id: ID(`user_from_rated_Movie_ratings_User_to`)} }] } }]) }] }]) }] } }] ,to: [(`user`)-[`user_to_relation`:`FRIEND_OF`]->(`user_to`:`User`) | user_to_relation { .since ,User: user_to {_id: ID(`user_to`), .name ,rated: [(`user_to`)-[`user_to_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_to_rated_relation { .rating ,Movie: head([(:`User`)-[`user_to_rated_relation`]->(`user_to_rated_Movie`:`Movie`:`u_user-id`:`newMovieLabel`) | user_to_rated_Movie {_id: ID(`user_to_rated_Movie`)}]) }] } }] } } AS `user` +MATCH (`user`:`User`) RETURN `user` {_id: ID(`user`), .name ,friends: {FROM: [(`user`)<-[`user_from_relation`:`FRIEND_OF`]-(`user_from`:`User`) | user_from_relation { .since ,User: user_from {_id: ID(`user_from`), .name ,rated: [(`user_from`)-[`user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_relation { .rating ,Movie: head([(:`User`)-[`user_from_rated_relation`]->(`user_from_rated_Movie`:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_Movie {_id: ID(`user_from_rated_Movie`),ratings: [(`user_from_rated_Movie`)<-[`user_from_rated_Movie_ratings_relation`:`RATED`]-(:`User`) | user_from_rated_Movie_ratings_relation { .rating ,User: head([(:`Movie`:`u_user-id`:`newMovieLabel`)<-[`user_from_rated_Movie_ratings_relation`]-(`user_from_rated_Movie_ratings_User`:`User`) | user_from_rated_Movie_ratings_User {_id: ID(`user_from_rated_Movie_ratings_User`),friends: {FROM: [(`user_from_rated_Movie_ratings_User`)<-[`user_from_rated_Movie_ratings_User_from_relation`:`FRIEND_OF`]-(`user_from_rated_Movie_ratings_User_from`:`User`) | user_from_rated_Movie_ratings_User_from_relation { .since ,User: user_from_rated_Movie_ratings_User_from {_id: ID(`user_from_rated_Movie_ratings_User_from`)} }] ,to: [(`user_from_rated_Movie_ratings_User`)-[`user_from_rated_Movie_ratings_User_to_relation`:`FRIEND_OF`]->(`user_from_rated_Movie_ratings_User_to`:`User`) | user_from_rated_Movie_ratings_User_to_relation { .since ,User: user_from_rated_Movie_ratings_User_to {_id: ID(`user_from_rated_Movie_ratings_User_to`)} }] } }]) }] }]) }] } }] ,to: [(`user`)-[`user_to_relation`:`FRIEND_OF`]->(`user_to`:`User`) | user_to_relation { .since ,User: user_to {_id: ID(`user_to`), .name ,rated: [(`user_to`)-[`user_to_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_to_rated_relation { .rating ,Movie: head([(:`User`)-[`user_to_rated_relation`]->(`user_to_rated_Movie`:`Movie`:`u_user-id`:`newMovieLabel`) | user_to_rated_Movie {_id: ID(`user_to_rated_Movie`)}]) }] } }] } } AS `user` ---- === query relation type with argument @@ -1846,7 +1846,7 @@ query { } } } - + ---- .Cypher params @@ -1858,7 +1858,7 @@ query { .Cypher [source,cypher] ---- -MATCH (`user`:`User`) RETURN `user` { .userId , .name ,friends: {from: [(`user`)<-[`user_from_relation`:`FRIEND_OF`{since:$1_since}]-(`user_from`:`User`) | user_from_relation { .since ,User: user_from { .name } }] ,to: [(`user`)-[`user_to_relation`:`FRIEND_OF`{since:$3_since}]->(`user_to`:`User`) | user_to_relation { .since ,User: user_to { .name } }] } } AS `user` +MATCH (`user`:`User`) RETURN `user` { .userId , .name ,friends: {FROM: [(`user`)<-[`user_from_relation`:`FRIEND_OF`{since:$1_since}]-(`user_from`:`User`) | user_from_relation { .since ,User: user_from { .name } }] ,to: [(`user`)-[`user_to_relation`:`FRIEND_OF`{since:$3_since}]->(`user_to`:`User`) | user_to_relation { .since ,User: user_to { .name } }] } } AS `user` ---- === query using inline fragment @@ -1881,7 +1881,7 @@ MATCH (`user`:`User`) RETURN `user` { .userId , .name ,friends: {from: [(`user`) } } } - + ---- .Cypher params @@ -1913,10 +1913,10 @@ mutation { timezone: "-08:00", formatted: "10:30:01.002003004-08:00" } - date: { - year: 2018, - month: 11, - day: 23 + date: { + year: 2018, + month: 11, + day: 23 }, datetime: { year: 2018, @@ -2017,7 +2017,7 @@ mutation { CREATE (`temporalNode`:`TemporalNode` {datetime: datetime($params.datetime),time: time($params.time),date: date($params.date),localtime: localtime($params.localtime),localdatetime: localdatetime($params.localdatetime)}) RETURN `temporalNode` {_id: ID(`temporalNode`),time: { hour: `temporalNode`.time.hour , minute: `temporalNode`.time.minute , second: `temporalNode`.time.second , millisecond: `temporalNode`.time.millisecond , microsecond: `temporalNode`.time.microsecond , nanosecond: `temporalNode`.time.nanosecond , timezone: `temporalNode`.time.timezone , formatted: toString(`temporalNode`.time) },date: { year: `temporalNode`.date.year , month: `temporalNode`.date.month , day: `temporalNode`.date.day , formatted: toString(`temporalNode`.date) },datetime: { year: `temporalNode`.datetime.year , month: `temporalNode`.datetime.month , day: `temporalNode`.datetime.day , hour: `temporalNode`.datetime.hour , minute: `temporalNode`.datetime.minute , second: `temporalNode`.datetime.second , millisecond: `temporalNode`.datetime.millisecond , microsecond: `temporalNode`.datetime.microsecond , nanosecond: `temporalNode`.datetime.nanosecond , timezone: `temporalNode`.datetime.timezone , formatted: toString(`temporalNode`.datetime) },localtime: { hour: `temporalNode`.localtime.hour , minute: `temporalNode`.localtime.minute , second: `temporalNode`.localtime.second , millisecond: `temporalNode`.localtime.millisecond , microsecond: `temporalNode`.localtime.microsecond , nanosecond: `temporalNode`.localtime.nanosecond , formatted: toString(`temporalNode`.localtime) },localdatetime: { year: `temporalNode`.localdatetime.year , month: `temporalNode`.localdatetime.month , day: `temporalNode`.localdatetime.day , hour: `temporalNode`.localdatetime.hour , minute: `temporalNode`.localdatetime.minute , second: `temporalNode`.localdatetime.second , millisecond: `temporalNode`.localdatetime.millisecond , microsecond: `temporalNode`.localdatetime.microsecond , nanosecond: `temporalNode`.localdatetime.nanosecond , formatted: toString(`temporalNode`.localdatetime) }} AS `temporalNode` - + ---- === Query node with temporal properties using temporal arguments @@ -2037,10 +2037,10 @@ query { timezone: "-08:00", formatted: "10:30:01.002003004-08:00" } - date: { - year: 2018, - month: 11, - day: 23 + date: { + year: 2018, + month: 11, + day: 23 } datetime: { year: 2018, @@ -2396,7 +2396,7 @@ mutation { .Cypher [source,cypher] ---- -MATCH (`temporalNode`:`TemporalNode`) WHERE `temporalNode`.datetime.year = $params.datetime.year AND `temporalNode`.datetime.month = $params.datetime.month AND `temporalNode`.datetime.day = $params.datetime.day AND `temporalNode`.datetime.hour = $params.datetime.hour AND `temporalNode`.datetime.minute = $params.datetime.minute AND `temporalNode`.datetime.second = $params.datetime.second AND `temporalNode`.datetime.millisecond = $params.datetime.millisecond AND `temporalNode`.datetime.microsecond = $params.datetime.microsecond AND `temporalNode`.datetime.nanosecond = $params.datetime.nanosecond AND `temporalNode`.datetime.timezone = $params.datetime.timezone +MATCH (`temporalNode`:`TemporalNode`) WHERE `temporalNode`.datetime.year = $params.datetime.year AND `temporalNode`.datetime.month = $params.datetime.month AND `temporalNode`.datetime.day = $params.datetime.day AND `temporalNode`.datetime.hour = $params.datetime.hour AND `temporalNode`.datetime.minute = $params.datetime.minute AND `temporalNode`.datetime.second = $params.datetime.second AND `temporalNode`.datetime.millisecond = $params.datetime.millisecond AND `temporalNode`.datetime.microsecond = $params.datetime.microsecond AND `temporalNode`.datetime.nanosecond = $params.datetime.nanosecond AND `temporalNode`.datetime.timezone = $params.datetime.timezone SET `temporalNode` += {name:$params.name,localdatetime: localdatetime($params.localdatetime)} RETURN `temporalNode` {_id: ID(`temporalNode`), .name ,time: { hour: `temporalNode`.time.hour , minute: `temporalNode`.time.minute , second: `temporalNode`.time.second , millisecond: `temporalNode`.time.millisecond , microsecond: `temporalNode`.time.microsecond , nanosecond: `temporalNode`.time.nanosecond , timezone: `temporalNode`.time.timezone , formatted: toString(`temporalNode`.time) },date: { year: `temporalNode`.date.year , month: `temporalNode`.date.month , day: `temporalNode`.date.day , formatted: toString(`temporalNode`.date) },datetime: { year: `temporalNode`.datetime.year , month: `temporalNode`.datetime.month , day: `temporalNode`.datetime.day , hour: `temporalNode`.datetime.hour , minute: `temporalNode`.datetime.minute , second: `temporalNode`.datetime.second , millisecond: `temporalNode`.datetime.millisecond , microsecond: `temporalNode`.datetime.microsecond , nanosecond: `temporalNode`.datetime.nanosecond , timezone: `temporalNode`.datetime.timezone , formatted: toString(`temporalNode`.datetime) },localtime: { hour: `temporalNode`.localtime.hour , minute: `temporalNode`.localtime.minute , second: `temporalNode`.localtime.second , millisecond: `temporalNode`.localtime.millisecond , microsecond: `temporalNode`.localtime.microsecond , nanosecond: `temporalNode`.localtime.nanosecond , formatted: toString(`temporalNode`.localtime) },localdatetime: { year: `temporalNode`.localdatetime.year , month: `temporalNode`.localdatetime.month , day: `temporalNode`.localdatetime.day , hour: `temporalNode`.localdatetime.hour , minute: `temporalNode`.localdatetime.minute , second: `temporalNode`.localdatetime.second , millisecond: `temporalNode`.localdatetime.millisecond , microsecond: `temporalNode`.localdatetime.microsecond , nanosecond: `temporalNode`.localdatetime.nanosecond , formatted: toString(`temporalNode`.localdatetime) }} AS `temporalNode` ---- @@ -2455,7 +2455,7 @@ mutation { .Cypher [source,cypher] ---- -MATCH (`temporalNode`:`TemporalNode`) WHERE `temporalNode`.datetime.year = $params.datetime.year AND `temporalNode`.datetime.month = $params.datetime.month AND `temporalNode`.datetime.day = $params.datetime.day AND `temporalNode`.datetime.hour = $params.datetime.hour AND `temporalNode`.datetime.minute = $params.datetime.minute AND `temporalNode`.datetime.second = $params.datetime.second AND `temporalNode`.datetime.millisecond = $params.datetime.millisecond AND `temporalNode`.datetime.microsecond = $params.datetime.microsecond AND `temporalNode`.datetime.nanosecond = $params.datetime.nanosecond AND `temporalNode`.datetime.timezone = $params.datetime.timezone +MATCH (`temporalNode`:`TemporalNode`) WHERE `temporalNode`.datetime.year = $params.datetime.year AND `temporalNode`.datetime.month = $params.datetime.month AND `temporalNode`.datetime.day = $params.datetime.day AND `temporalNode`.datetime.hour = $params.datetime.hour AND `temporalNode`.datetime.minute = $params.datetime.minute AND `temporalNode`.datetime.second = $params.datetime.second AND `temporalNode`.datetime.millisecond = $params.datetime.millisecond AND `temporalNode`.datetime.microsecond = $params.datetime.microsecond AND `temporalNode`.datetime.nanosecond = $params.datetime.nanosecond AND `temporalNode`.datetime.timezone = $params.datetime.timezone SET `temporalNode` += {localdatetimes: [value IN $params.localdatetimes | localdatetime(value)]} RETURN `temporalNode` {_id: ID(`temporalNode`), .name ,localdatetimes: reduce(a = [], TEMPORAL_INSTANCE IN temporalNode.localdatetimes | a + { year: TEMPORAL_INSTANCE.year , month: TEMPORAL_INSTANCE.month , day: TEMPORAL_INSTANCE.day , hour: TEMPORAL_INSTANCE.hour , minute: TEMPORAL_INSTANCE.minute , second: TEMPORAL_INSTANCE.second , millisecond: TEMPORAL_INSTANCE.millisecond , microsecond: TEMPORAL_INSTANCE.microsecond , nanosecond: TEMPORAL_INSTANCE.nanosecond , formatted: toString(TEMPORAL_INSTANCE) })} AS `temporalNode` ---- @@ -2706,11 +2706,11 @@ mutation { [source,cypher] ---- - MATCH (`temporalNode_from`:`TemporalNode`) WHERE `temporalNode_from`.datetime.year = $from.datetime.year AND `temporalNode_from`.datetime.month = $from.datetime.month AND `temporalNode_from`.datetime.day = $from.datetime.day AND `temporalNode_from`.datetime.hour = $from.datetime.hour AND `temporalNode_from`.datetime.minute = $from.datetime.minute AND `temporalNode_from`.datetime.second = $from.datetime.second AND `temporalNode_from`.datetime.millisecond = $from.datetime.millisecond AND `temporalNode_from`.datetime.microsecond = $from.datetime.microsecond AND `temporalNode_from`.datetime.nanosecond = $from.datetime.nanosecond AND `temporalNode_from`.datetime.timezone = $from.datetime.timezone - MATCH (`temporalNode_to`:`TemporalNode`) WHERE `temporalNode_to`.datetime.year = $to.datetime.year AND `temporalNode_to`.datetime.month = $to.datetime.month AND `temporalNode_to`.datetime.day = $to.datetime.day AND `temporalNode_to`.datetime.hour = $to.datetime.hour AND `temporalNode_to`.datetime.minute = $to.datetime.minute AND `temporalNode_to`.datetime.second = $to.datetime.second AND `temporalNode_to`.datetime.millisecond = $to.datetime.millisecond AND `temporalNode_to`.datetime.microsecond = $to.datetime.microsecond AND `temporalNode_to`.datetime.nanosecond = $to.datetime.nanosecond AND `temporalNode_to`.datetime.timezone = $to.datetime.timezone + MATCH (`temporalNode_from`:`TemporalNode`) WHERE `temporalNode_from`.datetime.year = $from.datetime.year AND `temporalNode_from`.datetime.month = $from.datetime.month AND `temporalNode_from`.datetime.day = $from.datetime.day AND `temporalNode_from`.datetime.hour = $from.datetime.hour AND `temporalNode_from`.datetime.minute = $from.datetime.minute AND `temporalNode_from`.datetime.second = $from.datetime.second AND `temporalNode_from`.datetime.millisecond = $from.datetime.millisecond AND `temporalNode_from`.datetime.microsecond = $from.datetime.microsecond AND `temporalNode_from`.datetime.nanosecond = $from.datetime.nanosecond AND `temporalNode_from`.datetime.timezone = $from.datetime.timezone + MATCH (`temporalNode_to`:`TemporalNode`) WHERE `temporalNode_to`.datetime.year = $to.datetime.year AND `temporalNode_to`.datetime.month = $to.datetime.month AND `temporalNode_to`.datetime.day = $to.datetime.day AND `temporalNode_to`.datetime.hour = $to.datetime.hour AND `temporalNode_to`.datetime.minute = $to.datetime.minute AND `temporalNode_to`.datetime.second = $to.datetime.second AND `temporalNode_to`.datetime.millisecond = $to.datetime.millisecond AND `temporalNode_to`.datetime.microsecond = $to.datetime.microsecond AND `temporalNode_to`.datetime.nanosecond = $to.datetime.nanosecond AND `temporalNode_to`.datetime.timezone = $to.datetime.timezone CREATE (`temporalNode_from`)-[`temporal_relation`:`TEMPORAL`]->(`temporalNode_to`) - RETURN `temporal_relation` { from: `temporalNode_from` {_id: ID(`temporalNode_from`),time: { hour: `temporalNode_from`.time.hour , minute: `temporalNode_from`.time.minute , second: `temporalNode_from`.time.second , millisecond: `temporalNode_from`.time.millisecond , microsecond: `temporalNode_from`.time.microsecond , nanosecond: `temporalNode_from`.time.nanosecond , timezone: `temporalNode_from`.time.timezone , formatted: toString(`temporalNode_from`.time) },date: { year: `temporalNode_from`.date.year , month: `temporalNode_from`.date.month , day: `temporalNode_from`.date.day , formatted: toString(`temporalNode_from`.date) },datetime: { year: `temporalNode_from`.datetime.year , month: `temporalNode_from`.datetime.month , day: `temporalNode_from`.datetime.day , hour: `temporalNode_from`.datetime.hour , minute: `temporalNode_from`.datetime.minute , second: `temporalNode_from`.datetime.second , millisecond: `temporalNode_from`.datetime.millisecond , microsecond: `temporalNode_from`.datetime.microsecond , nanosecond: `temporalNode_from`.datetime.nanosecond , timezone: `temporalNode_from`.datetime.timezone , formatted: toString(`temporalNode_from`.datetime) },localtime: { hour: `temporalNode_from`.localtime.hour , minute: `temporalNode_from`.localtime.minute , second: `temporalNode_from`.localtime.second , millisecond: `temporalNode_from`.localtime.millisecond , microsecond: `temporalNode_from`.localtime.microsecond , nanosecond: `temporalNode_from`.localtime.nanosecond , formatted: toString(`temporalNode_from`.localtime) },localdatetime: { year: `temporalNode_from`.localdatetime.year , month: `temporalNode_from`.localdatetime.month , day: `temporalNode_from`.localdatetime.day , hour: `temporalNode_from`.localdatetime.hour , minute: `temporalNode_from`.localdatetime.minute , second: `temporalNode_from`.localdatetime.second , millisecond: `temporalNode_from`.localdatetime.millisecond , microsecond: `temporalNode_from`.localdatetime.microsecond , nanosecond: `temporalNode_from`.localdatetime.nanosecond , formatted: toString(`temporalNode_from`.localdatetime) }} ,to: `temporalNode_to` {_id: ID(`temporalNode_to`),time: { hour: `temporalNode_to`.time.hour , minute: `temporalNode_to`.time.minute , second: `temporalNode_to`.time.second , millisecond: `temporalNode_to`.time.millisecond , microsecond: `temporalNode_to`.time.microsecond , nanosecond: `temporalNode_to`.time.nanosecond , timezone: `temporalNode_to`.time.timezone , formatted: toString(`temporalNode_to`.time) },date: { year: `temporalNode_to`.date.year , month: `temporalNode_to`.date.month , day: `temporalNode_to`.date.day , formatted: toString(`temporalNode_to`.date) },datetime: { year: `temporalNode_to`.datetime.year , month: `temporalNode_to`.datetime.month , day: `temporalNode_to`.datetime.day , hour: `temporalNode_to`.datetime.hour , minute: `temporalNode_to`.datetime.minute , second: `temporalNode_to`.datetime.second , millisecond: `temporalNode_to`.datetime.millisecond , microsecond: `temporalNode_to`.datetime.microsecond , nanosecond: `temporalNode_to`.datetime.nanosecond , timezone: `temporalNode_to`.datetime.timezone , formatted: toString(`temporalNode_to`.datetime) },localtime: { hour: `temporalNode_to`.localtime.hour , minute: `temporalNode_to`.localtime.minute , second: `temporalNode_to`.localtime.second , millisecond: `temporalNode_to`.localtime.millisecond , microsecond: `temporalNode_to`.localtime.microsecond , nanosecond: `temporalNode_to`.localtime.nanosecond , formatted: toString(`temporalNode_to`.localtime) },localdatetime: { year: `temporalNode_to`.localdatetime.year , month: `temporalNode_to`.localdatetime.month , day: `temporalNode_to`.localdatetime.day , hour: `temporalNode_to`.localdatetime.hour , minute: `temporalNode_to`.localdatetime.minute , second: `temporalNode_to`.localdatetime.second , millisecond: `temporalNode_to`.localdatetime.millisecond , microsecond: `temporalNode_to`.localdatetime.microsecond , nanosecond: `temporalNode_to`.localdatetime.nanosecond , formatted: toString(`temporalNode_to`.localdatetime) }} } AS `_AddTemporalNodeTemporalNodesPayload`; - + RETURN `temporal_relation` { FROM: `temporalNode_from` {_id: ID(`temporalNode_from`),time: { hour: `temporalNode_from`.time.hour , minute: `temporalNode_from`.time.minute , second: `temporalNode_from`.time.second , millisecond: `temporalNode_from`.time.millisecond , microsecond: `temporalNode_from`.time.microsecond , nanosecond: `temporalNode_from`.time.nanosecond , timezone: `temporalNode_from`.time.timezone , formatted: toString(`temporalNode_from`.time) },date: { year: `temporalNode_from`.date.year , month: `temporalNode_from`.date.month , day: `temporalNode_from`.date.day , formatted: toString(`temporalNode_from`.date) },datetime: { year: `temporalNode_from`.datetime.year , month: `temporalNode_from`.datetime.month , day: `temporalNode_from`.datetime.day , hour: `temporalNode_from`.datetime.hour , minute: `temporalNode_from`.datetime.minute , second: `temporalNode_from`.datetime.second , millisecond: `temporalNode_from`.datetime.millisecond , microsecond: `temporalNode_from`.datetime.microsecond , nanosecond: `temporalNode_from`.datetime.nanosecond , timezone: `temporalNode_from`.datetime.timezone , formatted: toString(`temporalNode_from`.datetime) },localtime: { hour: `temporalNode_from`.localtime.hour , minute: `temporalNode_from`.localtime.minute , second: `temporalNode_from`.localtime.second , millisecond: `temporalNode_from`.localtime.millisecond , microsecond: `temporalNode_from`.localtime.microsecond , nanosecond: `temporalNode_from`.localtime.nanosecond , formatted: toString(`temporalNode_from`.localtime) },localdatetime: { year: `temporalNode_from`.localdatetime.year , month: `temporalNode_from`.localdatetime.month , day: `temporalNode_from`.localdatetime.day , hour: `temporalNode_from`.localdatetime.hour , minute: `temporalNode_from`.localdatetime.minute , second: `temporalNode_from`.localdatetime.second , millisecond: `temporalNode_from`.localdatetime.millisecond , microsecond: `temporalNode_from`.localdatetime.microsecond , nanosecond: `temporalNode_from`.localdatetime.nanosecond , formatted: toString(`temporalNode_from`.localdatetime) }} ,to: `temporalNode_to` {_id: ID(`temporalNode_to`),time: { hour: `temporalNode_to`.time.hour , minute: `temporalNode_to`.time.minute , second: `temporalNode_to`.time.second , millisecond: `temporalNode_to`.time.millisecond , microsecond: `temporalNode_to`.time.microsecond , nanosecond: `temporalNode_to`.time.nanosecond , timezone: `temporalNode_to`.time.timezone , formatted: toString(`temporalNode_to`.time) },date: { year: `temporalNode_to`.date.year , month: `temporalNode_to`.date.month , day: `temporalNode_to`.date.day , formatted: toString(`temporalNode_to`.date) },datetime: { year: `temporalNode_to`.datetime.year , month: `temporalNode_to`.datetime.month , day: `temporalNode_to`.datetime.day , hour: `temporalNode_to`.datetime.hour , minute: `temporalNode_to`.datetime.minute , second: `temporalNode_to`.datetime.second , millisecond: `temporalNode_to`.datetime.millisecond , microsecond: `temporalNode_to`.datetime.microsecond , nanosecond: `temporalNode_to`.datetime.nanosecond , timezone: `temporalNode_to`.datetime.timezone , formatted: toString(`temporalNode_to`.datetime) },localtime: { hour: `temporalNode_to`.localtime.hour , minute: `temporalNode_to`.localtime.minute , second: `temporalNode_to`.localtime.second , millisecond: `temporalNode_to`.localtime.millisecond , microsecond: `temporalNode_to`.localtime.microsecond , nanosecond: `temporalNode_to`.localtime.nanosecond , formatted: toString(`temporalNode_to`.localtime) },localdatetime: { year: `temporalNode_to`.localdatetime.year , month: `temporalNode_to`.localdatetime.month , day: `temporalNode_to`.localdatetime.day , hour: `temporalNode_to`.localdatetime.hour , minute: `temporalNode_to`.localdatetime.minute , second: `temporalNode_to`.localdatetime.second , millisecond: `temporalNode_to`.localdatetime.millisecond , microsecond: `temporalNode_to`.localdatetime.microsecond , nanosecond: `temporalNode_to`.localdatetime.nanosecond , formatted: toString(`temporalNode_to`.localdatetime) }} } AS `_AddTemporalNodeTemporalNodesPayload`; + ---- === Remove relationship mutation using temporal property node selection @@ -2869,13 +2869,13 @@ mutation { [source,cypher] ---- - MATCH (`temporalNode_from`:`TemporalNode`) WHERE `temporalNode_from`.datetime.year = $from.datetime.year AND `temporalNode_from`.datetime.month = $from.datetime.month AND `temporalNode_from`.datetime.day = $from.datetime.day AND `temporalNode_from`.datetime.hour = $from.datetime.hour AND `temporalNode_from`.datetime.minute = $from.datetime.minute AND `temporalNode_from`.datetime.second = $from.datetime.second AND `temporalNode_from`.datetime.millisecond = $from.datetime.millisecond AND `temporalNode_from`.datetime.microsecond = $from.datetime.microsecond AND `temporalNode_from`.datetime.nanosecond = $from.datetime.nanosecond AND `temporalNode_from`.datetime.timezone = $from.datetime.timezone - MATCH (`temporalNode_to`:`TemporalNode`) WHERE `temporalNode_to`.datetime.year = $to.datetime.year AND `temporalNode_to`.datetime.month = $to.datetime.month AND `temporalNode_to`.datetime.day = $to.datetime.day AND `temporalNode_to`.datetime.hour = $to.datetime.hour AND `temporalNode_to`.datetime.minute = $to.datetime.minute AND `temporalNode_to`.datetime.second = $to.datetime.second AND `temporalNode_to`.datetime.millisecond = $to.datetime.millisecond AND `temporalNode_to`.datetime.microsecond = $to.datetime.microsecond AND `temporalNode_to`.datetime.nanosecond = $to.datetime.nanosecond AND `temporalNode_to`.datetime.timezone = $to.datetime.timezone + MATCH (`temporalNode_from`:`TemporalNode`) WHERE `temporalNode_from`.datetime.year = $from.datetime.year AND `temporalNode_from`.datetime.month = $from.datetime.month AND `temporalNode_from`.datetime.day = $from.datetime.day AND `temporalNode_from`.datetime.hour = $from.datetime.hour AND `temporalNode_from`.datetime.minute = $from.datetime.minute AND `temporalNode_from`.datetime.second = $from.datetime.second AND `temporalNode_from`.datetime.millisecond = $from.datetime.millisecond AND `temporalNode_from`.datetime.microsecond = $from.datetime.microsecond AND `temporalNode_from`.datetime.nanosecond = $from.datetime.nanosecond AND `temporalNode_from`.datetime.timezone = $from.datetime.timezone + MATCH (`temporalNode_to`:`TemporalNode`) WHERE `temporalNode_to`.datetime.year = $to.datetime.year AND `temporalNode_to`.datetime.month = $to.datetime.month AND `temporalNode_to`.datetime.day = $to.datetime.day AND `temporalNode_to`.datetime.hour = $to.datetime.hour AND `temporalNode_to`.datetime.minute = $to.datetime.minute AND `temporalNode_to`.datetime.second = $to.datetime.second AND `temporalNode_to`.datetime.millisecond = $to.datetime.millisecond AND `temporalNode_to`.datetime.microsecond = $to.datetime.microsecond AND `temporalNode_to`.datetime.nanosecond = $to.datetime.nanosecond AND `temporalNode_to`.datetime.timezone = $to.datetime.timezone OPTIONAL MATCH (`temporalNode_from`)-[`temporalNode_fromtemporalNode_to`:`TEMPORAL`]->(`temporalNode_to`) DELETE `temporalNode_fromtemporalNode_to` WITH COUNT(*) AS scope, `temporalNode_from` AS `_temporalNode_from`, `temporalNode_to` AS `_temporalNode_to` - RETURN {from: `_temporalNode_from` {_id: ID(`_temporalNode_from`),time: { hour: `_temporalNode_from`.time.hour , minute: `_temporalNode_from`.time.minute , second: `_temporalNode_from`.time.second , millisecond: `_temporalNode_from`.time.millisecond , microsecond: `_temporalNode_from`.time.microsecond , nanosecond: `_temporalNode_from`.time.nanosecond , timezone: `_temporalNode_from`.time.timezone , formatted: toString(`_temporalNode_from`.time) },date: { year: `_temporalNode_from`.date.year , month: `_temporalNode_from`.date.month , day: `_temporalNode_from`.date.day , formatted: toString(`_temporalNode_from`.date) },datetime: { year: `_temporalNode_from`.datetime.year , month: `_temporalNode_from`.datetime.month , day: `_temporalNode_from`.datetime.day , hour: `_temporalNode_from`.datetime.hour , minute: `_temporalNode_from`.datetime.minute , second: `_temporalNode_from`.datetime.second , millisecond: `_temporalNode_from`.datetime.millisecond , microsecond: `_temporalNode_from`.datetime.microsecond , nanosecond: `_temporalNode_from`.datetime.nanosecond , timezone: `_temporalNode_from`.datetime.timezone , formatted: toString(`_temporalNode_from`.datetime) },localtime: { hour: `_temporalNode_from`.localtime.hour , minute: `_temporalNode_from`.localtime.minute , second: `_temporalNode_from`.localtime.second , millisecond: `_temporalNode_from`.localtime.millisecond , microsecond: `_temporalNode_from`.localtime.microsecond , nanosecond: `_temporalNode_from`.localtime.nanosecond , formatted: toString(`_temporalNode_from`.localtime) },localdatetime: { year: `_temporalNode_from`.localdatetime.year , month: `_temporalNode_from`.localdatetime.month , day: `_temporalNode_from`.localdatetime.day , hour: `_temporalNode_from`.localdatetime.hour , minute: `_temporalNode_from`.localdatetime.minute , second: `_temporalNode_from`.localdatetime.second , millisecond: `_temporalNode_from`.localdatetime.millisecond , microsecond: `_temporalNode_from`.localdatetime.microsecond , nanosecond: `_temporalNode_from`.localdatetime.nanosecond , formatted: toString(`_temporalNode_from`.localdatetime) }} ,to: `_temporalNode_to` {_id: ID(`_temporalNode_to`),time: { hour: `_temporalNode_to`.time.hour , minute: `_temporalNode_to`.time.minute , second: `_temporalNode_to`.time.second , millisecond: `_temporalNode_to`.time.millisecond , microsecond: `_temporalNode_to`.time.microsecond , nanosecond: `_temporalNode_to`.time.nanosecond , timezone: `_temporalNode_to`.time.timezone , formatted: toString(`_temporalNode_to`.time) },date: { year: `_temporalNode_to`.date.year , month: `_temporalNode_to`.date.month , day: `_temporalNode_to`.date.day , formatted: toString(`_temporalNode_to`.date) },datetime: { year: `_temporalNode_to`.datetime.year , month: `_temporalNode_to`.datetime.month , day: `_temporalNode_to`.datetime.day , hour: `_temporalNode_to`.datetime.hour , minute: `_temporalNode_to`.datetime.minute , second: `_temporalNode_to`.datetime.second , millisecond: `_temporalNode_to`.datetime.millisecond , microsecond: `_temporalNode_to`.datetime.microsecond , nanosecond: `_temporalNode_to`.datetime.nanosecond , timezone: `_temporalNode_to`.datetime.timezone , formatted: toString(`_temporalNode_to`.datetime) },localtime: { hour: `_temporalNode_to`.localtime.hour , minute: `_temporalNode_to`.localtime.minute , second: `_temporalNode_to`.localtime.second , millisecond: `_temporalNode_to`.localtime.millisecond , microsecond: `_temporalNode_to`.localtime.microsecond , nanosecond: `_temporalNode_to`.localtime.nanosecond , formatted: toString(`_temporalNode_to`.localtime) },localdatetime: { year: `_temporalNode_to`.localdatetime.year , month: `_temporalNode_to`.localdatetime.month , day: `_temporalNode_to`.localdatetime.day , hour: `_temporalNode_to`.localdatetime.hour , minute: `_temporalNode_to`.localdatetime.minute , second: `_temporalNode_to`.localdatetime.second , millisecond: `_temporalNode_to`.localdatetime.millisecond , microsecond: `_temporalNode_to`.localdatetime.microsecond , nanosecond: `_temporalNode_to`.localdatetime.nanosecond , formatted: toString(`_temporalNode_to`.localdatetime) }} } AS `_RemoveTemporalNodeTemporalNodesPayload`; - + RETURN {FROM: `_temporalNode_from` {_id: ID(`_temporalNode_from`),time: { hour: `_temporalNode_from`.time.hour , minute: `_temporalNode_from`.time.minute , second: `_temporalNode_from`.time.second , millisecond: `_temporalNode_from`.time.millisecond , microsecond: `_temporalNode_from`.time.microsecond , nanosecond: `_temporalNode_from`.time.nanosecond , timezone: `_temporalNode_from`.time.timezone , formatted: toString(`_temporalNode_from`.time) },date: { year: `_temporalNode_from`.date.year , month: `_temporalNode_from`.date.month , day: `_temporalNode_from`.date.day , formatted: toString(`_temporalNode_from`.date) },datetime: { year: `_temporalNode_from`.datetime.year , month: `_temporalNode_from`.datetime.month , day: `_temporalNode_from`.datetime.day , hour: `_temporalNode_from`.datetime.hour , minute: `_temporalNode_from`.datetime.minute , second: `_temporalNode_from`.datetime.second , millisecond: `_temporalNode_from`.datetime.millisecond , microsecond: `_temporalNode_from`.datetime.microsecond , nanosecond: `_temporalNode_from`.datetime.nanosecond , timezone: `_temporalNode_from`.datetime.timezone , formatted: toString(`_temporalNode_from`.datetime) },localtime: { hour: `_temporalNode_from`.localtime.hour , minute: `_temporalNode_from`.localtime.minute , second: `_temporalNode_from`.localtime.second , millisecond: `_temporalNode_from`.localtime.millisecond , microsecond: `_temporalNode_from`.localtime.microsecond , nanosecond: `_temporalNode_from`.localtime.nanosecond , formatted: toString(`_temporalNode_from`.localtime) },localdatetime: { year: `_temporalNode_from`.localdatetime.year , month: `_temporalNode_from`.localdatetime.month , day: `_temporalNode_from`.localdatetime.day , hour: `_temporalNode_from`.localdatetime.hour , minute: `_temporalNode_from`.localdatetime.minute , second: `_temporalNode_from`.localdatetime.second , millisecond: `_temporalNode_from`.localdatetime.millisecond , microsecond: `_temporalNode_from`.localdatetime.microsecond , nanosecond: `_temporalNode_from`.localdatetime.nanosecond , formatted: toString(`_temporalNode_from`.localdatetime) }} ,to: `_temporalNode_to` {_id: ID(`_temporalNode_to`),time: { hour: `_temporalNode_to`.time.hour , minute: `_temporalNode_to`.time.minute , second: `_temporalNode_to`.time.second , millisecond: `_temporalNode_to`.time.millisecond , microsecond: `_temporalNode_to`.time.microsecond , nanosecond: `_temporalNode_to`.time.nanosecond , timezone: `_temporalNode_to`.time.timezone , formatted: toString(`_temporalNode_to`.time) },date: { year: `_temporalNode_to`.date.year , month: `_temporalNode_to`.date.month , day: `_temporalNode_to`.date.day , formatted: toString(`_temporalNode_to`.date) },datetime: { year: `_temporalNode_to`.datetime.year , month: `_temporalNode_to`.datetime.month , day: `_temporalNode_to`.datetime.day , hour: `_temporalNode_to`.datetime.hour , minute: `_temporalNode_to`.datetime.minute , second: `_temporalNode_to`.datetime.second , millisecond: `_temporalNode_to`.datetime.millisecond , microsecond: `_temporalNode_to`.datetime.microsecond , nanosecond: `_temporalNode_to`.datetime.nanosecond , timezone: `_temporalNode_to`.datetime.timezone , formatted: toString(`_temporalNode_to`.datetime) },localtime: { hour: `_temporalNode_to`.localtime.hour , minute: `_temporalNode_to`.localtime.minute , second: `_temporalNode_to`.localtime.second , millisecond: `_temporalNode_to`.localtime.millisecond , microsecond: `_temporalNode_to`.localtime.microsecond , nanosecond: `_temporalNode_to`.localtime.nanosecond , formatted: toString(`_temporalNode_to`.localtime) },localdatetime: { year: `_temporalNode_to`.localdatetime.year , month: `_temporalNode_to`.localdatetime.month , day: `_temporalNode_to`.localdatetime.day , hour: `_temporalNode_to`.localdatetime.hour , minute: `_temporalNode_to`.localdatetime.minute , second: `_temporalNode_to`.localdatetime.second , millisecond: `_temporalNode_to`.localdatetime.millisecond , microsecond: `_temporalNode_to`.localdatetime.microsecond , nanosecond: `_temporalNode_to`.localdatetime.nanosecond , formatted: toString(`_temporalNode_to`.localdatetime) }} } AS `_RemoveTemporalNodeTemporalNodesPayload`; + ---- === Query relationship with temporal properties @@ -2894,7 +2894,7 @@ query { } User { _id - name + name } } } @@ -2926,9 +2926,9 @@ mutation { to: { movieId: "04b85fa3-7c78-4e65-9830-97dad60aa746" }, - data: { - rating: 5, - time: { + data: { + rating: 5, + time: { hour: 10, minute: 30, second: 1, @@ -2938,10 +2938,10 @@ mutation { timezone: "-08:00", formatted: "10:30:01.002003004-08:00" }, - date: { - year: 2017, - month: 11, - day: 23 + date: { + year: 2017, + month: 11, + day: 23 }, datetime: { year: 2020, @@ -3064,8 +3064,8 @@ mutation { MATCH (`user_from`:`User` {userId: $from.userId}) MATCH (`movie_to`:`Movie`:`u_user-id`:`newMovieLabel` {movieId: $to.movieId}) CREATE (`user_from`)-[`rated_relation`:`RATED` {rating:$data.rating,time: time($data.time),date: date($data.date),datetime: datetime($data.datetime),localtime: localtime($data.localtime),localdatetime: localdatetime($data.localdatetime)}]->(`movie_to`) - RETURN `rated_relation` { time: { hour: `rated_relation`.time.hour , minute: `rated_relation`.time.minute , second: `rated_relation`.time.second , millisecond: `rated_relation`.time.millisecond , microsecond: `rated_relation`.time.microsecond , nanosecond: `rated_relation`.time.nanosecond , timezone: `rated_relation`.time.timezone , formatted: toString(`rated_relation`.time) },date: { year: `rated_relation`.date.year , month: `rated_relation`.date.month , day: `rated_relation`.date.day , formatted: toString(`rated_relation`.date) },datetime: { year: `rated_relation`.datetime.year , month: `rated_relation`.datetime.month , day: `rated_relation`.datetime.day , hour: `rated_relation`.datetime.hour , minute: `rated_relation`.datetime.minute , second: `rated_relation`.datetime.second , millisecond: `rated_relation`.datetime.millisecond , microsecond: `rated_relation`.datetime.microsecond , nanosecond: `rated_relation`.datetime.nanosecond , timezone: `rated_relation`.datetime.timezone , formatted: toString(`rated_relation`.datetime) },localtime: { hour: `rated_relation`.localtime.hour , minute: `rated_relation`.localtime.minute , second: `rated_relation`.localtime.second , millisecond: `rated_relation`.localtime.millisecond , microsecond: `rated_relation`.localtime.microsecond , nanosecond: `rated_relation`.localtime.nanosecond , formatted: toString(`rated_relation`.localtime) },localdatetime: { year: `rated_relation`.localdatetime.year , month: `rated_relation`.localdatetime.month , day: `rated_relation`.localdatetime.day , hour: `rated_relation`.localdatetime.hour , minute: `rated_relation`.localdatetime.minute , second: `rated_relation`.localdatetime.second , millisecond: `rated_relation`.localdatetime.millisecond , microsecond: `rated_relation`.localdatetime.microsecond , nanosecond: `rated_relation`.localdatetime.nanosecond , formatted: toString(`rated_relation`.localdatetime) },from: `user_from` {_id: ID(`user_from`), .userId , .name ,rated: [(`user_from`)-[`user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_relation {datetime: { year: `user_from_rated_relation`.datetime.year }}] } ,to: `movie_to` {_id: ID(`movie_to`), .movieId , .title ,ratings: [(`movie_to`)<-[`movie_to_ratings_relation`:`RATED`]-(:`User`) | movie_to_ratings_relation {datetime: { year: `movie_to_ratings_relation`.datetime.year }}] } } AS `_AddUserRatedPayload`; - + RETURN `rated_relation` { time: { hour: `rated_relation`.time.hour , minute: `rated_relation`.time.minute , second: `rated_relation`.time.second , millisecond: `rated_relation`.time.millisecond , microsecond: `rated_relation`.time.microsecond , nanosecond: `rated_relation`.time.nanosecond , timezone: `rated_relation`.time.timezone , formatted: toString(`rated_relation`.time) },date: { year: `rated_relation`.date.year , month: `rated_relation`.date.month , day: `rated_relation`.date.day , formatted: toString(`rated_relation`.date) },datetime: { year: `rated_relation`.datetime.year , month: `rated_relation`.datetime.month , day: `rated_relation`.datetime.day , hour: `rated_relation`.datetime.hour , minute: `rated_relation`.datetime.minute , second: `rated_relation`.datetime.second , millisecond: `rated_relation`.datetime.millisecond , microsecond: `rated_relation`.datetime.microsecond , nanosecond: `rated_relation`.datetime.nanosecond , timezone: `rated_relation`.datetime.timezone , formatted: toString(`rated_relation`.datetime) },localtime: { hour: `rated_relation`.localtime.hour , minute: `rated_relation`.localtime.minute , second: `rated_relation`.localtime.second , millisecond: `rated_relation`.localtime.millisecond , microsecond: `rated_relation`.localtime.microsecond , nanosecond: `rated_relation`.localtime.nanosecond , formatted: toString(`rated_relation`.localtime) },localdatetime: { year: `rated_relation`.localdatetime.year , month: `rated_relation`.localdatetime.month , day: `rated_relation`.localdatetime.day , hour: `rated_relation`.localdatetime.hour , minute: `rated_relation`.localdatetime.minute , second: `rated_relation`.localdatetime.second , millisecond: `rated_relation`.localdatetime.millisecond , microsecond: `rated_relation`.localdatetime.microsecond , nanosecond: `rated_relation`.localdatetime.nanosecond , formatted: toString(`rated_relation`.localdatetime) },FROM: `user_from` {_id: ID(`user_from`), .userId , .name ,rated: [(`user_from`)-[`user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_relation {datetime: { year: `user_from_rated_relation`.datetime.year }}] } ,to: `movie_to` {_id: ID(`movie_to`), .movieId , .title ,ratings: [(`movie_to`)<-[`movie_to_ratings_relation`:`RATED`]-(:`User`) | movie_to_ratings_relation {datetime: { year: `movie_to_ratings_relation`.datetime.year }}] } } AS `_AddUserRatedPayload`; + ---- === Add relationship mutation with list properties @@ -3149,8 +3149,8 @@ mutation { MATCH (`user_from`:`User` {userId: $from.userId}) MATCH (`movie_to`:`Movie`:`u_user-id`:`newMovieLabel` {movieId: $to.movieId}) CREATE (`user_from`)-[`rated_relation`:`RATED` {ratings:$data.ratings,datetimes: [value IN $data.datetimes | datetime(value)]}]->(`movie_to`) - RETURN `rated_relation` { .ratings ,datetimes: reduce(a = [], TEMPORAL_INSTANCE IN rated_relation.datetimes | a + { year: TEMPORAL_INSTANCE.year , month: TEMPORAL_INSTANCE.month , day: TEMPORAL_INSTANCE.day , hour: TEMPORAL_INSTANCE.hour , minute: TEMPORAL_INSTANCE.minute , second: TEMPORAL_INSTANCE.second , millisecond: TEMPORAL_INSTANCE.millisecond , microsecond: TEMPORAL_INSTANCE.microsecond , nanosecond: TEMPORAL_INSTANCE.nanosecond , timezone: TEMPORAL_INSTANCE.timezone , formatted: toString(TEMPORAL_INSTANCE) }),from: `user_from` {_id: ID(`user_from`), .userId , .name ,rated: [(`user_from`)-[`user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_relation {datetime: { year: `user_from_rated_relation`.datetime.year }}] } ,to: `movie_to` {_id: ID(`movie_to`), .movieId , .title ,ratings: [(`movie_to`)<-[`movie_to_ratings_relation`:`RATED`]-(:`User`) | movie_to_ratings_relation {datetime: { year: `movie_to_ratings_relation`.datetime.year }}] } } AS `_AddUserRatedPayload`; - + RETURN `rated_relation` { .ratings ,datetimes: reduce(a = [], TEMPORAL_INSTANCE IN rated_relation.datetimes | a + { year: TEMPORAL_INSTANCE.year , month: TEMPORAL_INSTANCE.month , day: TEMPORAL_INSTANCE.day , hour: TEMPORAL_INSTANCE.hour , minute: TEMPORAL_INSTANCE.minute , second: TEMPORAL_INSTANCE.second , millisecond: TEMPORAL_INSTANCE.millisecond , microsecond: TEMPORAL_INSTANCE.microsecond , nanosecond: TEMPORAL_INSTANCE.nanosecond , timezone: TEMPORAL_INSTANCE.timezone , formatted: toString(TEMPORAL_INSTANCE) }),FROM: `user_from` {_id: ID(`user_from`), .userId , .name ,rated: [(`user_from`)-[`user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_relation {datetime: { year: `user_from_rated_relation`.datetime.year }}] } ,to: `movie_to` {_id: ID(`movie_to`), .movieId , .title ,ratings: [(`movie_to`)<-[`movie_to_ratings_relation`:`RATED`]-(:`User`) | movie_to_ratings_relation {datetime: { year: `movie_to_ratings_relation`.datetime.year }}] } } AS `_AddUserRatedPayload`; + ---- === Add reflexive relationship mutation with temporal properties @@ -3366,8 +3366,8 @@ mutation { MATCH (`user_from`:`User` {userId: $from.userId}) MATCH (`user_to`:`User` {userId: $to.userId}) CREATE (`user_from`)-[`friend_of_relation`:`FRIEND_OF` {since:$data.since,time: time($data.time),date: date($data.date),datetime: datetime($data.datetime),datetimes: [value IN $data.datetimes | datetime(value)],localtime: localtime($data.localtime),localdatetime: localdatetime($data.localdatetime)}]->(`user_to`) - RETURN `friend_of_relation` { from: `user_from` {_id: ID(`user_from`), .userId , .name ,friends: {from: [(`user_from`)<-[`user_from_from_relation`:`FRIEND_OF`]-(`user_from_from`:`User`) | user_from_from_relation { .since ,User: user_from_from {_id: ID(`user_from_from`), .name ,friends: {from: [(`user_from_from`)<-[`user_from_from_from_relation`:`FRIEND_OF`]-(`user_from_from_from`:`User`) | user_from_from_from_relation { .since ,User: user_from_from_from {_id: ID(`user_from_from_from`), .name } }] ,to: [(`user_from_from`)-[`user_from_from_to_relation`:`FRIEND_OF`]->(`user_from_from_to`:`User`) | user_from_from_to_relation { .since ,User: user_from_from_to {_id: ID(`user_from_from_to`), .name } }] } } }] ,to: [(`user_from`)-[`user_from_to_relation`:`FRIEND_OF`]->(`user_from_to`:`User`) | user_from_to_relation { .since ,datetime: { year: `user_from_to_relation`.datetime.year },User: user_from_to {_id: ID(`user_from_to`), .name } }] } } ,to: `user_to` {_id: ID(`user_to`), .name ,friends: {from: [(`user_to`)<-[`user_to_from_relation`:`FRIEND_OF`]-(`user_to_from`:`User`) | user_to_from_relation { .since ,User: user_to_from {_id: ID(`user_to_from`), .name } }] ,to: [(`user_to`)-[`user_to_to_relation`:`FRIEND_OF`]->(`user_to_to`:`User`) | user_to_to_relation { .since ,User: user_to_to {_id: ID(`user_to_to`), .name } }] } } , .since ,time: { hour: `friend_of_relation`.time.hour , minute: `friend_of_relation`.time.minute , second: `friend_of_relation`.time.second , millisecond: `friend_of_relation`.time.millisecond , microsecond: `friend_of_relation`.time.microsecond , nanosecond: `friend_of_relation`.time.nanosecond , timezone: `friend_of_relation`.time.timezone , formatted: toString(`friend_of_relation`.time) },date: { year: `friend_of_relation`.date.year , month: `friend_of_relation`.date.month , day: `friend_of_relation`.date.day , formatted: toString(`friend_of_relation`.date) },datetime: { year: `friend_of_relation`.datetime.year , month: `friend_of_relation`.datetime.month , day: `friend_of_relation`.datetime.day , hour: `friend_of_relation`.datetime.hour , minute: `friend_of_relation`.datetime.minute , second: `friend_of_relation`.datetime.second , millisecond: `friend_of_relation`.datetime.millisecond , microsecond: `friend_of_relation`.datetime.microsecond , nanosecond: `friend_of_relation`.datetime.nanosecond , timezone: `friend_of_relation`.datetime.timezone , formatted: toString(`friend_of_relation`.datetime) },datetimes: reduce(a = [], TEMPORAL_INSTANCE IN friend_of_relation.datetimes | a + { year: TEMPORAL_INSTANCE.year , month: TEMPORAL_INSTANCE.month , day: TEMPORAL_INSTANCE.day , hour: TEMPORAL_INSTANCE.hour , minute: TEMPORAL_INSTANCE.minute , second: TEMPORAL_INSTANCE.second , millisecond: TEMPORAL_INSTANCE.millisecond , microsecond: TEMPORAL_INSTANCE.microsecond , nanosecond: TEMPORAL_INSTANCE.nanosecond , timezone: TEMPORAL_INSTANCE.timezone , formatted: toString(TEMPORAL_INSTANCE) }),localtime: { hour: `friend_of_relation`.localtime.hour , minute: `friend_of_relation`.localtime.minute , second: `friend_of_relation`.localtime.second , millisecond: `friend_of_relation`.localtime.millisecond , microsecond: `friend_of_relation`.localtime.microsecond , nanosecond: `friend_of_relation`.localtime.nanosecond , formatted: toString(`friend_of_relation`.localtime) },localdatetime: { year: `friend_of_relation`.localdatetime.year , month: `friend_of_relation`.localdatetime.month , day: `friend_of_relation`.localdatetime.day , hour: `friend_of_relation`.localdatetime.hour , minute: `friend_of_relation`.localdatetime.minute , second: `friend_of_relation`.localdatetime.second , millisecond: `friend_of_relation`.localdatetime.millisecond , microsecond: `friend_of_relation`.localdatetime.microsecond , nanosecond: `friend_of_relation`.localdatetime.nanosecond , formatted: toString(`friend_of_relation`.localdatetime) } } AS `_AddUserFriendsPayload`; - + RETURN `friend_of_relation` { FROM: `user_from` {_id: ID(`user_from`), .userId , .name ,friends: {FROM: [(`user_from`)<-[`user_from_from_relation`:`FRIEND_OF`]-(`user_from_from`:`User`) | user_from_from_relation { .since ,User: user_from_from {_id: ID(`user_from_from`), .name ,friends: {FROM: [(`user_from_from`)<-[`user_from_from_from_relation`:`FRIEND_OF`]-(`user_from_from_from`:`User`) | user_from_from_from_relation { .since ,User: user_from_from_from {_id: ID(`user_from_from_from`), .name } }] ,to: [(`user_from_from`)-[`user_from_from_to_relation`:`FRIEND_OF`]->(`user_from_from_to`:`User`) | user_from_from_to_relation { .since ,User: user_from_from_to {_id: ID(`user_from_from_to`), .name } }] } } }] ,to: [(`user_from`)-[`user_from_to_relation`:`FRIEND_OF`]->(`user_from_to`:`User`) | user_from_to_relation { .since ,datetime: { year: `user_from_to_relation`.datetime.year },User: user_from_to {_id: ID(`user_from_to`), .name } }] } } ,to: `user_to` {_id: ID(`user_to`), .name ,friends: {FROM: [(`user_to`)<-[`user_to_from_relation`:`FRIEND_OF`]-(`user_to_from`:`User`) | user_to_from_relation { .since ,User: user_to_from {_id: ID(`user_to_from`), .name } }] ,to: [(`user_to`)-[`user_to_to_relation`:`FRIEND_OF`]->(`user_to_to`:`User`) | user_to_to_relation { .since ,User: user_to_to {_id: ID(`user_to_to`), .name } }] } } , .since ,time: { hour: `friend_of_relation`.time.hour , minute: `friend_of_relation`.time.minute , second: `friend_of_relation`.time.second , millisecond: `friend_of_relation`.time.millisecond , microsecond: `friend_of_relation`.time.microsecond , nanosecond: `friend_of_relation`.time.nanosecond , timezone: `friend_of_relation`.time.timezone , formatted: toString(`friend_of_relation`.time) },date: { year: `friend_of_relation`.date.year , month: `friend_of_relation`.date.month , day: `friend_of_relation`.date.day , formatted: toString(`friend_of_relation`.date) },datetime: { year: `friend_of_relation`.datetime.year , month: `friend_of_relation`.datetime.month , day: `friend_of_relation`.datetime.day , hour: `friend_of_relation`.datetime.hour , minute: `friend_of_relation`.datetime.minute , second: `friend_of_relation`.datetime.second , millisecond: `friend_of_relation`.datetime.millisecond , microsecond: `friend_of_relation`.datetime.microsecond , nanosecond: `friend_of_relation`.datetime.nanosecond , timezone: `friend_of_relation`.datetime.timezone , formatted: toString(`friend_of_relation`.datetime) },datetimes: reduce(a = [], TEMPORAL_INSTANCE IN friend_of_relation.datetimes | a + { year: TEMPORAL_INSTANCE.year , month: TEMPORAL_INSTANCE.month , day: TEMPORAL_INSTANCE.day , hour: TEMPORAL_INSTANCE.hour , minute: TEMPORAL_INSTANCE.minute , second: TEMPORAL_INSTANCE.second , millisecond: TEMPORAL_INSTANCE.millisecond , microsecond: TEMPORAL_INSTANCE.microsecond , nanosecond: TEMPORAL_INSTANCE.nanosecond , timezone: TEMPORAL_INSTANCE.timezone , formatted: toString(TEMPORAL_INSTANCE) }),localtime: { hour: `friend_of_relation`.localtime.hour , minute: `friend_of_relation`.localtime.minute , second: `friend_of_relation`.localtime.second , millisecond: `friend_of_relation`.localtime.millisecond , microsecond: `friend_of_relation`.localtime.microsecond , nanosecond: `friend_of_relation`.localtime.nanosecond , formatted: toString(`friend_of_relation`.localtime) },localdatetime: { year: `friend_of_relation`.localdatetime.year , month: `friend_of_relation`.localdatetime.month , day: `friend_of_relation`.localdatetime.day , hour: `friend_of_relation`.localdatetime.hour , minute: `friend_of_relation`.localdatetime.minute , second: `friend_of_relation`.localdatetime.second , millisecond: `friend_of_relation`.localdatetime.millisecond , microsecond: `friend_of_relation`.localdatetime.microsecond , nanosecond: `friend_of_relation`.localdatetime.nanosecond , formatted: toString(`friend_of_relation`.localdatetime) } } AS `_AddUserFriendsPayload`; + ---- === Remove relationship mutation for relation type field @@ -3422,8 +3422,8 @@ mutation { OPTIONAL MATCH (`user_from`)-[`user_frommovie_to`:`RATED`]->(`movie_to`) DELETE `user_frommovie_to` WITH COUNT(*) AS scope, `user_from` AS `_user_from`, `movie_to` AS `_movie_to` - RETURN {from: `_user_from` {_id: ID(`_user_from`), .userId , .name ,rated: [(`_user_from`)-[`_user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | _user_from_rated_relation {datetime: { year: `_user_from_rated_relation`.datetime.year },Movie: head([(:`User`)-[`_user_from_rated_relation`]->(`_user_from_rated_Movie`:`Movie`:`u_user-id`:`newMovieLabel`) | _user_from_rated_Movie { .title }]) }] } ,to: `_movie_to` {_id: ID(`_movie_to`), .movieId , .title ,ratings: [(`_movie_to`)<-[`_movie_to_ratings_relation`:`RATED`]-(:`User`) | _movie_to_ratings_relation {datetime: { year: `_movie_to_ratings_relation`.datetime.year }}] } } AS `_RemoveUserRatedPayload`; - + RETURN {FROM: `_user_from` {_id: ID(`_user_from`), .userId , .name ,rated: [(`_user_from`)-[`_user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | _user_from_rated_relation {datetime: { year: `_user_from_rated_relation`.datetime.year },Movie: head([(:`User`)-[`_user_from_rated_relation`]->(`_user_from_rated_Movie`:`Movie`:`u_user-id`:`newMovieLabel`) | _user_from_rated_Movie { .title }]) }] } ,to: `_movie_to` {_id: ID(`_movie_to`), .movieId , .title ,ratings: [(`_movie_to`)<-[`_movie_to_ratings_relation`:`RATED`]-(:`User`) | _movie_to_ratings_relation {datetime: { year: `_movie_to_ratings_relation`.datetime.year }}] } } AS `_RemoveUserRatedPayload`; + ---- === Query nested temporal properties on reflexive relationship using temporal arguments @@ -3673,7 +3673,7 @@ query { .Cypher [source,cypher] ---- -MATCH (`user`:`User`) RETURN `user` { .userId , .name ,friends: {from: [(`user`)<-[`user_from_relation`:`FRIEND_OF`]-(`user_from`:`User`) WHERE user_from_relation.time = time($1_time.formatted) AND user_from_relation.date.year = $1_date.year AND user_from_relation.date.month = $1_date.month AND user_from_relation.date.day = $1_date.day AND user_from_relation.datetime.year = $1_datetime.year AND user_from_relation.datetime.month = $1_datetime.month AND user_from_relation.datetime.day = $1_datetime.day AND user_from_relation.datetime.hour = $1_datetime.hour AND user_from_relation.datetime.minute = $1_datetime.minute AND user_from_relation.datetime.second = $1_datetime.second AND user_from_relation.datetime.millisecond = $1_datetime.millisecond AND user_from_relation.datetime.microsecond = $1_datetime.microsecond AND user_from_relation.datetime.nanosecond = $1_datetime.nanosecond AND user_from_relation.datetime.timezone = $1_datetime.timezone AND user_from_relation.localtime.hour = $1_localtime.hour AND user_from_relation.localtime.minute = $1_localtime.minute AND user_from_relation.localtime.second = $1_localtime.second AND user_from_relation.localtime.millisecond = $1_localtime.millisecond AND user_from_relation.localtime.microsecond = $1_localtime.microsecond AND user_from_relation.localtime.nanosecond = $1_localtime.nanosecond AND user_from_relation.localdatetime.year = $1_localdatetime.year AND user_from_relation.localdatetime.month = $1_localdatetime.month AND user_from_relation.localdatetime.day = $1_localdatetime.day AND user_from_relation.localdatetime.hour = $1_localdatetime.hour AND user_from_relation.localdatetime.minute = $1_localdatetime.minute AND user_from_relation.localdatetime.second = $1_localdatetime.second AND user_from_relation.localdatetime.millisecond = $1_localdatetime.millisecond AND user_from_relation.localdatetime.microsecond = $1_localdatetime.microsecond AND user_from_relation.localdatetime.nanosecond = $1_localdatetime.nanosecond | user_from_relation { .since ,time: { hour: `user_from_relation`.time.hour , minute: `user_from_relation`.time.minute , second: `user_from_relation`.time.second , millisecond: `user_from_relation`.time.millisecond , microsecond: `user_from_relation`.time.microsecond , nanosecond: `user_from_relation`.time.nanosecond , timezone: `user_from_relation`.time.timezone , formatted: toString(`user_from_relation`.time) },date: { year: `user_from_relation`.date.year , month: `user_from_relation`.date.month , day: `user_from_relation`.date.day , formatted: toString(`user_from_relation`.date) },datetime: { year: `user_from_relation`.datetime.year , month: `user_from_relation`.datetime.month , day: `user_from_relation`.datetime.day , hour: `user_from_relation`.datetime.hour , minute: `user_from_relation`.datetime.minute , second: `user_from_relation`.datetime.second , millisecond: `user_from_relation`.datetime.millisecond , microsecond: `user_from_relation`.datetime.microsecond , nanosecond: `user_from_relation`.datetime.nanosecond , timezone: `user_from_relation`.datetime.timezone , formatted: toString(`user_from_relation`.datetime) },datetimes: reduce(a = [], TEMPORAL_INSTANCE IN user_from_relation.datetimes | a + { year: TEMPORAL_INSTANCE.year , month: TEMPORAL_INSTANCE.month , day: TEMPORAL_INSTANCE.day , hour: TEMPORAL_INSTANCE.hour , minute: TEMPORAL_INSTANCE.minute , second: TEMPORAL_INSTANCE.second , millisecond: TEMPORAL_INSTANCE.millisecond , microsecond: TEMPORAL_INSTANCE.microsecond , nanosecond: TEMPORAL_INSTANCE.nanosecond , timezone: TEMPORAL_INSTANCE.timezone , formatted: toString(TEMPORAL_INSTANCE) }),localtime: { hour: `user_from_relation`.localtime.hour , minute: `user_from_relation`.localtime.minute , second: `user_from_relation`.localtime.second , millisecond: `user_from_relation`.localtime.millisecond , microsecond: `user_from_relation`.localtime.microsecond , nanosecond: `user_from_relation`.localtime.nanosecond , formatted: toString(`user_from_relation`.localtime) },localdatetime: { year: `user_from_relation`.localdatetime.year , month: `user_from_relation`.localdatetime.month , day: `user_from_relation`.localdatetime.day , hour: `user_from_relation`.localdatetime.hour , minute: `user_from_relation`.localdatetime.minute , second: `user_from_relation`.localdatetime.second , millisecond: `user_from_relation`.localdatetime.millisecond , microsecond: `user_from_relation`.localdatetime.microsecond , nanosecond: `user_from_relation`.localdatetime.nanosecond , formatted: toString(`user_from_relation`.localdatetime) },User: user_from {_id: ID(`user_from`), .userId ,rated: [(`user_from`)-[`user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_relation {datetime: { year: `user_from_rated_relation`.datetime.year }}] } }] ,to: [(`user`)-[`user_to_relation`:`FRIEND_OF`]->(`user_to`:`User`) WHERE user_to_relation.time = time($3_time.formatted) AND user_to_relation.date.year = $3_date.year AND user_to_relation.date.month = $3_date.month AND user_to_relation.date.day = $3_date.day AND user_to_relation.datetime.year = $3_datetime.year AND user_to_relation.datetime.month = $3_datetime.month AND user_to_relation.datetime.day = $3_datetime.day AND user_to_relation.datetime.hour = $3_datetime.hour AND user_to_relation.datetime.minute = $3_datetime.minute AND user_to_relation.datetime.second = $3_datetime.second AND user_to_relation.datetime.millisecond = $3_datetime.millisecond AND user_to_relation.datetime.microsecond = $3_datetime.microsecond AND user_to_relation.datetime.nanosecond = $3_datetime.nanosecond AND user_to_relation.datetime.timezone = $3_datetime.timezone AND user_to_relation.localtime.hour = $3_localtime.hour AND user_to_relation.localtime.minute = $3_localtime.minute AND user_to_relation.localtime.second = $3_localtime.second AND user_to_relation.localtime.millisecond = $3_localtime.millisecond AND user_to_relation.localtime.microsecond = $3_localtime.microsecond AND user_to_relation.localtime.nanosecond = $3_localtime.nanosecond AND user_to_relation.localdatetime.year = $3_localdatetime.year AND user_to_relation.localdatetime.month = $3_localdatetime.month AND user_to_relation.localdatetime.day = $3_localdatetime.day AND user_to_relation.localdatetime.hour = $3_localdatetime.hour AND user_to_relation.localdatetime.minute = $3_localdatetime.minute AND user_to_relation.localdatetime.second = $3_localdatetime.second AND user_to_relation.localdatetime.millisecond = $3_localdatetime.millisecond AND user_to_relation.localdatetime.microsecond = $3_localdatetime.microsecond AND user_to_relation.localdatetime.nanosecond = $3_localdatetime.nanosecond | user_to_relation { .since ,time: { hour: `user_to_relation`.time.hour , minute: `user_to_relation`.time.minute , second: `user_to_relation`.time.second , millisecond: `user_to_relation`.time.millisecond , microsecond: `user_to_relation`.time.microsecond , nanosecond: `user_to_relation`.time.nanosecond , timezone: `user_to_relation`.time.timezone , formatted: toString(`user_to_relation`.time) },date: { year: `user_to_relation`.date.year , month: `user_to_relation`.date.month , day: `user_to_relation`.date.day , formatted: toString(`user_to_relation`.date) },datetime: { year: `user_to_relation`.datetime.year , month: `user_to_relation`.datetime.month , day: `user_to_relation`.datetime.day , hour: `user_to_relation`.datetime.hour , minute: `user_to_relation`.datetime.minute , second: `user_to_relation`.datetime.second , millisecond: `user_to_relation`.datetime.millisecond , microsecond: `user_to_relation`.datetime.microsecond , nanosecond: `user_to_relation`.datetime.nanosecond , timezone: `user_to_relation`.datetime.timezone , formatted: toString(`user_to_relation`.datetime) },localtime: { hour: `user_to_relation`.localtime.hour , minute: `user_to_relation`.localtime.minute , second: `user_to_relation`.localtime.second , millisecond: `user_to_relation`.localtime.millisecond , microsecond: `user_to_relation`.localtime.microsecond , nanosecond: `user_to_relation`.localtime.nanosecond , formatted: toString(`user_to_relation`.localtime) },localdatetime: { year: `user_to_relation`.localdatetime.year , month: `user_to_relation`.localdatetime.month , day: `user_to_relation`.localdatetime.day , hour: `user_to_relation`.localdatetime.hour , minute: `user_to_relation`.localdatetime.minute , second: `user_to_relation`.localdatetime.second , millisecond: `user_to_relation`.localdatetime.millisecond , microsecond: `user_to_relation`.localdatetime.microsecond , nanosecond: `user_to_relation`.localdatetime.nanosecond , formatted: toString(`user_to_relation`.localdatetime) },User: user_to {_id: ID(`user_to`), .userId ,rated: [(`user_to`)-[`user_to_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_to_rated_relation {datetime: { year: `user_to_rated_relation`.datetime.year }}] } }] } } AS `user` +MATCH (`user`:`User`) RETURN `user` { .userId , .name ,friends: {FROM: [(`user`)<-[`user_from_relation`:`FRIEND_OF`]-(`user_from`:`User`) WHERE user_from_relation.time = time($1_time.formatted) AND user_from_relation.date.year = $1_date.year AND user_from_relation.date.month = $1_date.month AND user_from_relation.date.day = $1_date.day AND user_from_relation.datetime.year = $1_datetime.year AND user_from_relation.datetime.month = $1_datetime.month AND user_from_relation.datetime.day = $1_datetime.day AND user_from_relation.datetime.hour = $1_datetime.hour AND user_from_relation.datetime.minute = $1_datetime.minute AND user_from_relation.datetime.second = $1_datetime.second AND user_from_relation.datetime.millisecond = $1_datetime.millisecond AND user_from_relation.datetime.microsecond = $1_datetime.microsecond AND user_from_relation.datetime.nanosecond = $1_datetime.nanosecond AND user_from_relation.datetime.timezone = $1_datetime.timezone AND user_from_relation.localtime.hour = $1_localtime.hour AND user_from_relation.localtime.minute = $1_localtime.minute AND user_from_relation.localtime.second = $1_localtime.second AND user_from_relation.localtime.millisecond = $1_localtime.millisecond AND user_from_relation.localtime.microsecond = $1_localtime.microsecond AND user_from_relation.localtime.nanosecond = $1_localtime.nanosecond AND user_from_relation.localdatetime.year = $1_localdatetime.year AND user_from_relation.localdatetime.month = $1_localdatetime.month AND user_from_relation.localdatetime.day = $1_localdatetime.day AND user_from_relation.localdatetime.hour = $1_localdatetime.hour AND user_from_relation.localdatetime.minute = $1_localdatetime.minute AND user_from_relation.localdatetime.second = $1_localdatetime.second AND user_from_relation.localdatetime.millisecond = $1_localdatetime.millisecond AND user_from_relation.localdatetime.microsecond = $1_localdatetime.microsecond AND user_from_relation.localdatetime.nanosecond = $1_localdatetime.nanosecond | user_from_relation { .since ,time: { hour: `user_from_relation`.time.hour , minute: `user_from_relation`.time.minute , second: `user_from_relation`.time.second , millisecond: `user_from_relation`.time.millisecond , microsecond: `user_from_relation`.time.microsecond , nanosecond: `user_from_relation`.time.nanosecond , timezone: `user_from_relation`.time.timezone , formatted: toString(`user_from_relation`.time) },date: { year: `user_from_relation`.date.year , month: `user_from_relation`.date.month , day: `user_from_relation`.date.day , formatted: toString(`user_from_relation`.date) },datetime: { year: `user_from_relation`.datetime.year , month: `user_from_relation`.datetime.month , day: `user_from_relation`.datetime.day , hour: `user_from_relation`.datetime.hour , minute: `user_from_relation`.datetime.minute , second: `user_from_relation`.datetime.second , millisecond: `user_from_relation`.datetime.millisecond , microsecond: `user_from_relation`.datetime.microsecond , nanosecond: `user_from_relation`.datetime.nanosecond , timezone: `user_from_relation`.datetime.timezone , formatted: toString(`user_from_relation`.datetime) },datetimes: reduce(a = [], TEMPORAL_INSTANCE IN user_from_relation.datetimes | a + { year: TEMPORAL_INSTANCE.year , month: TEMPORAL_INSTANCE.month , day: TEMPORAL_INSTANCE.day , hour: TEMPORAL_INSTANCE.hour , minute: TEMPORAL_INSTANCE.minute , second: TEMPORAL_INSTANCE.second , millisecond: TEMPORAL_INSTANCE.millisecond , microsecond: TEMPORAL_INSTANCE.microsecond , nanosecond: TEMPORAL_INSTANCE.nanosecond , timezone: TEMPORAL_INSTANCE.timezone , formatted: toString(TEMPORAL_INSTANCE) }),localtime: { hour: `user_from_relation`.localtime.hour , minute: `user_from_relation`.localtime.minute , second: `user_from_relation`.localtime.second , millisecond: `user_from_relation`.localtime.millisecond , microsecond: `user_from_relation`.localtime.microsecond , nanosecond: `user_from_relation`.localtime.nanosecond , formatted: toString(`user_from_relation`.localtime) },localdatetime: { year: `user_from_relation`.localdatetime.year , month: `user_from_relation`.localdatetime.month , day: `user_from_relation`.localdatetime.day , hour: `user_from_relation`.localdatetime.hour , minute: `user_from_relation`.localdatetime.minute , second: `user_from_relation`.localdatetime.second , millisecond: `user_from_relation`.localdatetime.millisecond , microsecond: `user_from_relation`.localdatetime.microsecond , nanosecond: `user_from_relation`.localdatetime.nanosecond , formatted: toString(`user_from_relation`.localdatetime) },User: user_from {_id: ID(`user_from`), .userId ,rated: [(`user_from`)-[`user_from_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_from_rated_relation {datetime: { year: `user_from_rated_relation`.datetime.year }}] } }] ,to: [(`user`)-[`user_to_relation`:`FRIEND_OF`]->(`user_to`:`User`) WHERE user_to_relation.time = time($3_time.formatted) AND user_to_relation.date.year = $3_date.year AND user_to_relation.date.month = $3_date.month AND user_to_relation.date.day = $3_date.day AND user_to_relation.datetime.year = $3_datetime.year AND user_to_relation.datetime.month = $3_datetime.month AND user_to_relation.datetime.day = $3_datetime.day AND user_to_relation.datetime.hour = $3_datetime.hour AND user_to_relation.datetime.minute = $3_datetime.minute AND user_to_relation.datetime.second = $3_datetime.second AND user_to_relation.datetime.millisecond = $3_datetime.millisecond AND user_to_relation.datetime.microsecond = $3_datetime.microsecond AND user_to_relation.datetime.nanosecond = $3_datetime.nanosecond AND user_to_relation.datetime.timezone = $3_datetime.timezone AND user_to_relation.localtime.hour = $3_localtime.hour AND user_to_relation.localtime.minute = $3_localtime.minute AND user_to_relation.localtime.second = $3_localtime.second AND user_to_relation.localtime.millisecond = $3_localtime.millisecond AND user_to_relation.localtime.microsecond = $3_localtime.microsecond AND user_to_relation.localtime.nanosecond = $3_localtime.nanosecond AND user_to_relation.localdatetime.year = $3_localdatetime.year AND user_to_relation.localdatetime.month = $3_localdatetime.month AND user_to_relation.localdatetime.day = $3_localdatetime.day AND user_to_relation.localdatetime.hour = $3_localdatetime.hour AND user_to_relation.localdatetime.minute = $3_localdatetime.minute AND user_to_relation.localdatetime.second = $3_localdatetime.second AND user_to_relation.localdatetime.millisecond = $3_localdatetime.millisecond AND user_to_relation.localdatetime.microsecond = $3_localdatetime.microsecond AND user_to_relation.localdatetime.nanosecond = $3_localdatetime.nanosecond | user_to_relation { .since ,time: { hour: `user_to_relation`.time.hour , minute: `user_to_relation`.time.minute , second: `user_to_relation`.time.second , millisecond: `user_to_relation`.time.millisecond , microsecond: `user_to_relation`.time.microsecond , nanosecond: `user_to_relation`.time.nanosecond , timezone: `user_to_relation`.time.timezone , formatted: toString(`user_to_relation`.time) },date: { year: `user_to_relation`.date.year , month: `user_to_relation`.date.month , day: `user_to_relation`.date.day , formatted: toString(`user_to_relation`.date) },datetime: { year: `user_to_relation`.datetime.year , month: `user_to_relation`.datetime.month , day: `user_to_relation`.datetime.day , hour: `user_to_relation`.datetime.hour , minute: `user_to_relation`.datetime.minute , second: `user_to_relation`.datetime.second , millisecond: `user_to_relation`.datetime.millisecond , microsecond: `user_to_relation`.datetime.microsecond , nanosecond: `user_to_relation`.datetime.nanosecond , timezone: `user_to_relation`.datetime.timezone , formatted: toString(`user_to_relation`.datetime) },localtime: { hour: `user_to_relation`.localtime.hour , minute: `user_to_relation`.localtime.minute , second: `user_to_relation`.localtime.second , millisecond: `user_to_relation`.localtime.millisecond , microsecond: `user_to_relation`.localtime.microsecond , nanosecond: `user_to_relation`.localtime.nanosecond , formatted: toString(`user_to_relation`.localtime) },localdatetime: { year: `user_to_relation`.localdatetime.year , month: `user_to_relation`.localdatetime.month , day: `user_to_relation`.localdatetime.day , hour: `user_to_relation`.localdatetime.hour , minute: `user_to_relation`.localdatetime.minute , second: `user_to_relation`.localdatetime.second , millisecond: `user_to_relation`.localdatetime.millisecond , microsecond: `user_to_relation`.localdatetime.microsecond , nanosecond: `user_to_relation`.localdatetime.nanosecond , formatted: toString(`user_to_relation`.localdatetime) },User: user_to {_id: ID(`user_to`), .userId ,rated: [(`user_to`)-[`user_to_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_to_rated_relation {datetime: { year: `user_to_rated_relation`.datetime.year }}] } }] } } AS `user` ---- === Query nested temporal properties on relationships using temporal arguments @@ -4049,7 +4049,7 @@ mutation { CREATE (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {movieId: apoc.create.uuid(),title:$params.title,released: datetime($params.released)}) RETURN `movie` { .title } AS `movie` - + ---- === Create node with list arguments @@ -4111,7 +4111,7 @@ mutation { CREATE (`movie`:`Movie`:`u_user-id`:`newMovieLabel` {movieId: apoc.create.uuid(),title:$params.title,released: datetime($params.released),years:$params.years,titles:$params.titles,imdbRatings:$params.imdbRatings,releases: [value IN $params.releases | datetime(value)]}) RETURN `movie` { .movieId , .title , .titles , .imdbRatings , .years ,releases: reduce(a = [], TEMPORAL_INSTANCE IN movie.releases | a + { year: TEMPORAL_INSTANCE.year , month: TEMPORAL_INSTANCE.month , day: TEMPORAL_INSTANCE.day , hour: TEMPORAL_INSTANCE.hour , second: TEMPORAL_INSTANCE.second , formatted: toString(TEMPORAL_INSTANCE) })} AS `movie` - + ---- === Cypher array queries @@ -4193,7 +4193,7 @@ mutation { CREATE (`state`:`State` {name:$params.name}) RETURN `state` { .name } AS `state` - + ---- === Query node with ignored field @@ -4205,7 +4205,7 @@ query { State { name customField - } + } } ---- @@ -4299,7 +4299,7 @@ MATCH (`user`:`User`) WITH `user` ORDER BY user.name DESC RETURN `user` {_id: ID .Cypher [source,cypher] ---- -MATCH (`user`:`User`) RETURN `user` {_id: ID(`user`), .name ,currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user, cypherParams: $cypherParams, strArg: "Neo4j"}, false)} AS `user` ORDER BY user.currentUserId DESC +MATCH (`user`:`User`) RETURN `user` {_id: ID(`user`), .name ,currentUserId: apoc.CYPHER.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user, cypherParams: $cypherParams, strArg: "Neo4j"}, false)} AS `user` ORDER BY user.currentUserId DESC ---- === Query using enum orderBy @@ -4444,7 +4444,7 @@ query { .Cypher [source,cypher] ---- -MATCH (`user`:`User`) RETURN `user` { .userId ,currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user, cypherParams: $cypherParams, strArg: "Neo4j"}, false), .name } AS `user` +MATCH (`user`:`User`) RETURN `user` { .userId ,currentUserId: apoc.cypher.runFirstColumn('RETURN $cypherParams.currentUserId AS cypherParamsUserId', {this: user, cypherParams: $cypherParams, strArg: 'Neo4j'}, false), .name } AS `user` ---- === Handle nested @cypher fields that use cypherParams @@ -4492,7 +4492,7 @@ query { .Cypher [source,cypher] ---- -MATCH (`user`:`User`) RETURN `user` { .userId ,currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user, cypherParams: $cypherParams, strArg: "Neo4j"}, false), .name ,friends: {to: [(`user`)-[`user_to_relation`:`FRIEND_OF`]->(`user_to`:`User`) | user_to_relation { .since ,currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user_to_relation, cypherParams: $cypherParams}, false),User: user_to { .name ,currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user_to, cypherParams: $cypherParams, strArg: "Neo4j"}, false)} }] ,from: [(`user`)<-[`user_from_relation`:`FRIEND_OF`]-(`user_from`:`User`) | user_from_relation { .since ,currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user_from_relation, cypherParams: $cypherParams}, false)}] } ,rated: [(`user`)-[`user_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_rated_relation { .rating ,currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user_rated_relation, cypherParams: $cypherParams}, false)}] ,favorites: [(`user`)-[:`FAVORITED`]->(`user_favorites`:`Movie`:`u_user-id`:`newMovieLabel`) | user_favorites { .movieId ,currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user_favorites, cypherParams: $cypherParams}, false)}] } AS `user` +MATCH (`user`:`User`) RETURN `user` { .userId ,currentUserId: apoc.cypher.runFirstColumn('RETURN $cypherParams.currentUserId AS cypherParamsUserId', {this: user, cypherParams: $cypherParams, strArg: 'Neo4j'}, false), .name ,friends: {to: [(`user`)-[`user_to_relation`:`FRIEND_OF`]->(`user_to`:`User`) | user_to_relation { .since ,currentUserId: apoc.cypher.runFirstColumn('RETURN $cypherParams.currentUserId AS cypherParamsUserId', {this: user_to_relation, cypherParams: $cypherParams}, false),User: user_to { .name ,currentUserId: apoc.cypher.runFirstColumn('RETURN $cypherParams.currentUserId AS cypherParamsUserId', {this: user_to, cypherParams: $cypherParams, strArg: 'Neo4j'}, false)} }] ,from: [(`user`)<-[`user_from_relation`:`FRIEND_OF`]-(`user_from`:`User`) | user_from_relation { .since ,currentUserId: apoc.cypher.runFirstColumn('RETURN $cypherParams.currentUserId AS cypherParamsUserId', {this: user_from_relation, cypherParams: $cypherParams}, false)}] } ,rated: [(`user`)-[`user_rated_relation`:`RATED`]->(:`Movie`:`u_user-id`:`newMovieLabel`) | user_rated_relation { .rating ,currentUserId: apoc.cypher.runFirstColumn('RETURN $cypherParams.currentUserId AS cypherParamsUserId', {this: user_rated_relation, cypherParams: $cypherParams}, false)}] ,favorites: [(`user`)-[:`FAVORITED`]->(`user_favorites`:`Movie`:`u_user-id`:`newMovieLabel`) | user_favorites { .movieId ,currentUserId: apoc.cypher.runFirstColumn('RETURN $cypherParams.currentUserId AS cypherParamsUserId', {this: user_favorites, cypherParams: $cypherParams}, false)}] } AS `user` ---- === Handle @cypher query using cypherParams with String payload @@ -4514,7 +4514,7 @@ query { .Cypher [source,cypher] ---- -WITH apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS currentUserId", {offset:$offset, first:$first, cypherParams: $cypherParams}, True) AS x UNWIND x AS `string` RETURN `string` +WITH apoc.cypher.runFirstColumn('RETURN $cypherParams.currentUserId AS currentUserId', {offset:$offset, first:$first, cypherParams: $cypherParams}, true) AS x UNWIND x AS `string` RETURN `string` ---- === Handle @cypher query using cypherParams with Object payload @@ -4538,7 +4538,7 @@ query { .Cypher [source,cypher] ---- -WITH apoc.cypher.runFirstColumn("RETURN { userId: $cypherParams.currentUserId }", {offset:$offset, first:$first, cypherParams: $cypherParams}, True) AS x UNWIND x AS `currentUserId` RETURN `currentUserId` { .userId } AS `currentUserId` +WITH apoc.cypher.runFirstColumn('RETURN { userId: $cypherParams.currentUserId }', {offset:$offset, first:$first, cypherParams: $cypherParams}, true) AS x UNWIND x AS `currentUserId` RETURN `currentUserId` { .userId } AS `currentUserId` ---- === Handle the @cypher query with Boolean payload @@ -4560,7 +4560,7 @@ query { .Cypher [source,cypher] ---- -WITH apoc.cypher.runFirstColumn("RETURN true", {offset:$offset, first:$first, cypherParams: $cypherParams}, True) AS x UNWIND x AS `boolean` RETURN `boolean` +WITH apoc.cypher.runFirstColumn('RETURN true', {offset:$offset, first:$first, cypherParams: $cypherParams}, true) AS x UNWIND x AS `boolean` RETURN `boolean` ---- === Handle the @cypher query with Int payload @@ -4582,7 +4582,7 @@ query { .Cypher [source,cypher] ---- -WITH apoc.cypher.runFirstColumn("RETURN 1", {offset:$offset, first:$first, cypherParams: $cypherParams}, True) AS x UNWIND x AS `int` RETURN `int` +WITH apoc.cypher.runFirstColumn('RETURN 1', {offset:$offset, first:$first, cypherParams: $cypherParams}, true) AS x UNWIND x AS `int` RETURN `int` ---- === Handle the @cypher query with Float payload @@ -4604,7 +4604,7 @@ query { .Cypher [source,cypher] ---- -WITH apoc.cypher.runFirstColumn("RETURN 3.14", {offset:$offset, first:$first, cypherParams: $cypherParams}, True) AS x UNWIND x AS `float` RETURN `float` +WITH apoc.cypher.runFirstColumn('RETURN 3.14', {offset:$offset, first:$first, cypherParams: $cypherParams}, true) AS x UNWIND x AS `float` RETURN `float` ---- === Handle the @cypher query with String list payload @@ -4626,7 +4626,7 @@ query { .Cypher [source,cypher] ---- -WITH apoc.cypher.runFirstColumn("UNWIND ['hello', 'world'] AS stringList RETURN stringList", {offset:$offset, first:$first, cypherParams: $cypherParams}, True) AS x UNWIND x AS `string` RETURN `string` +WITH apoc.cypher.runFirstColumn("UNWIND ['hello', 'world'] AS stringList RETURN stringList", {offset:$offset, first:$first, cypherParams: $cypherParams}, true) AS x UNWIND x AS `string` RETURN `string` ---- === Handle the @cypher query with Int list payload @@ -4648,7 +4648,7 @@ query { .Cypher [source,cypher] ---- -WITH apoc.cypher.runFirstColumn("UNWIND [1, 2, 3] AS intList RETURN intList", {offset:$offset, first:$first, cypherParams: $cypherParams}, True) AS x UNWIND x AS `int` RETURN `int` +WITH apoc.cypher.runFirstColumn('UNWIND [1, 2, 3] AS intList RETURN intList', {offset:$offset, first:$first, cypherParams: $cypherParams}, true) AS x UNWIND x AS `int` RETURN `int` ---- === Handle the @cypher query with Temporal payload @@ -4666,7 +4666,7 @@ query { second microsecond millisecond - nanosecond + nanosecond timezone formatted } @@ -4682,7 +4682,7 @@ query { .Cypher [source,cypher] ---- -WITH apoc.cypher.runFirstColumn("WITH datetime() AS now RETURN { year: now.year, month: now.month , day: now.day , hour: now.hour , minute: now.minute , second: now.second , millisecond: now.millisecond , microsecond: now.microsecond , nanosecond: now.nanosecond , timezone: now.timezone , formatted: toString(now) }", {offset:$offset, first:$first, cypherParams: $cypherParams}, True) AS x UNWIND x AS `_Neo4jDateTime` RETURN `_Neo4jDateTime` +WITH apoc.cypher.runFirstColumn('WITH datetime() AS now RETURN { year: now.year, month: now.month , day: now.day , hour: now.hour , minute: now.minute , second: now.second , millisecond: now.millisecond , microsecond: now.microsecond , nanosecond: now.nanosecond , timezone: now.timezone , formatted: toString(now) }', {offset:$offset, first:$first, cypherParams: $cypherParams}, true) AS x UNWIND x AS `_Neo4jDateTime` RETURN `_Neo4jDateTime` ---- === Handle @cypher mutation using cypherParams with String payload @@ -4704,9 +4704,9 @@ mutation { .Cypher [source,cypher] ---- -CALL apoc.cypher.doIt("RETURN $cypherParams.currentUserId", {first:$first, offset:$offset, cypherParams: $cypherParams}) YIELD value +CALL apoc.cypher.doIt('RETURN $cypherParams.currentUserId', {first:$first, offset:$offset, cypherParams: $cypherParams}) YIELD value WITH apoc.map.values(value, [keys(value)[0]])[0] AS `string` - RETURN `string` + RETURN `string` ---- === Handle @cypher mutation using cypherParams with Object payload @@ -4730,7 +4730,7 @@ mutation { .Cypher [source,cypher] ---- -CALL apoc.cypher.doIt("RETURN { userId: $cypherParams.currentUserId }", {first:$first, offset:$offset, cypherParams: $cypherParams}) YIELD value +CALL apoc.cypher.doIt('RETURN { userId: $cypherParams.currentUserId }', {first:$first, offset:$offset, cypherParams: $cypherParams}) YIELD value WITH apoc.map.values(value, [keys(value)[0]])[0] AS `currentUserId` RETURN `currentUserId` { .userId } AS `currentUserId` ---- @@ -4756,7 +4756,7 @@ mutation { ---- CALL apoc.cypher.doIt("UNWIND ['hello', 'world'] AS stringList RETURN stringList", {first:$first, offset:$offset, cypherParams: $cypherParams}) YIELD value WITH apoc.map.values(value, [keys(value)[0]])[0] AS `string` - RETURN `string` + RETURN `string` ---- === Handle @cypher mutation with Temporal payload @@ -4774,7 +4774,7 @@ mutation { second microsecond millisecond - nanosecond + nanosecond timezone formatted } @@ -4790,9 +4790,9 @@ mutation { .Cypher [source,cypher] ---- -CALL apoc.cypher.doIt("WITH datetime() AS now RETURN { year: now.year, month: now.month , day: now.day , hour: now.hour , minute: now.minute , second: now.second , millisecond: now.millisecond , microsecond: now.microsecond , nanosecond: now.nanosecond , timezone: now.timezone , formatted: toString(now) }", {first:$first, offset:$offset, cypherParams: $cypherParams}) YIELD value +CALL apoc.cypher.doIt('WITH datetime() AS now RETURN { year: now.year, month: now.month , day: now.day , hour: now.hour , minute: now.minute , second: now.second , millisecond: now.millisecond , microsecond: now.microsecond , nanosecond: now.nanosecond , timezone: now.timezone , formatted: toString(now) }', {first:$first, offset:$offset, cypherParams: $cypherParams}) YIELD value WITH apoc.map.values(value, [keys(value)[0]])[0] AS `_Neo4jDateTime` - RETURN `_Neo4jDateTime` + RETURN `_Neo4jDateTime` ---- === Handle nested @cypher fields using parameterized arguments and cypherParams @@ -4829,7 +4829,7 @@ query someQuery( .Cypher [source,cypher] ---- -MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel`) RETURN `movie` {_id: ID(`movie`),currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: movie, cypherParams: $cypherParams, strArg: $1_strArg}, false),ratings: [(`movie`)<-[`movie_ratings_relation`:`RATED`]-(:`User`) | movie_ratings_relation {currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: movie_ratings_relation, cypherParams: $cypherParams, strArg: $2_strArg}, false),User: head([(:`Movie`:`u_user-id`:`newMovieLabel`)<-[`movie_ratings_relation`]-(`movie_ratings_User`:`User`) | movie_ratings_User { .name ,currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: movie_ratings_User, cypherParams: $cypherParams, strArg: $3_strArg, strInputArg: $3_strInputArg}, false)}]) }] } AS `movie` +MATCH (`movie`:`Movie`:`u_user-id`:`newMovieLabel`) RETURN `movie` {_id: ID(`movie`),currentUserId: apoc.CYPHER.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: movie, cypherParams: $cypherParams, strArg: $1_strArg}, false),ratings: [(`movie`)<-[`movie_ratings_relation`:`RATED`]-(:`User`) | movie_ratings_relation {currentUserId: apoc.CYPHER.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: movie_ratings_relation, cypherParams: $cypherParams, strArg: $2_strArg}, false),User: head([(:`Movie`:`u_user-id`:`newMovieLabel`)<-[`movie_ratings_relation`]-(`movie_ratings_User`:`User`) | movie_ratings_User { .name ,currentUserId: apoc.CYPHER.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: movie_ratings_User, cypherParams: $cypherParams, strArg: $3_strArg, strInputArg: $3_strInputArg}, false)}]) }] } AS `movie` ---- === Handle @cypher mutation with input type argument @@ -4851,9 +4851,9 @@ mutation someMutation($strArg: String, $strInputArg: strInput) { .Cypher [source,cypher] ---- -CALL apoc.cypher.doIt("RETURN $strInputArg.strArg", {strArg:$strArg, strInputArg:$strInputArg, first:$first, offset:$offset, cypherParams: $cypherParams}) YIELD value +CALL apoc.cypher.doIt('RETURN $strInputArg.strArg', {strArg:$strArg, strInputArg:$strInputArg, first:$first, offset:$offset, cypherParams: $cypherParams}) YIELD value WITH apoc.map.values(value, [keys(value)[0]])[0] AS `string` - RETURN `string` + RETURN `string` ---- === Handle the @cypher query with parameterized input type argument @@ -4875,7 +4875,7 @@ query someQuery ($strArg: String, $strInputArg: strInput) { .Cypher [source,cypher] ---- -WITH apoc.cypher.runFirstColumn("RETURN $strInputArg.strArg", {offset:$offset, first:$first, strArg:$strArg, strInputArg:$strInputArg, cypherParams: $cypherParams}, True) AS x UNWIND x AS `string` RETURN `string` +WITH apoc.cypher.runFirstColumn('RETURN $strInputArg.strArg', {offset:$offset, first:$first, strArg:$strArg, strInputArg:$strInputArg, cypherParams: $cypherParams}, true) AS x UNWIND x AS `string` RETURN `string` ---- === Handle the @cypher field on root query type with scalar payload, no args @@ -4899,7 +4899,7 @@ query { .Cypher [source,cypher] ---- -MATCH (`temporalNode`:`TemporalNode`) RETURN `temporalNode` {computedTimestamp: apoc.cypher.runFirstColumn("RETURN toString(datetime())", {this: temporalNode}, false)} AS `temporalNode` +MATCH (`temporalNode`:`TemporalNode`) RETURN `temporalNode` {computedTimestamp: apoc.cypher.runFirstColumn('RETURN toString(datetime())', {this: temporalNode}, false)} AS `temporalNode` ---- === Handle the @cypher field with parameterized value for field of input type argument @@ -4928,5 +4928,5 @@ query someQuery( .Cypher [source,cypher] ---- -MATCH (`user`:`User`) RETURN `user` { .name ,currentUserId: apoc.cypher.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user, cypherParams: $cypherParams, strArg: "Neo4j", strInputArg: $1_strInputArg}, false)} AS `user` +MATCH (`user`:`User`) RETURN `user` { .name ,currentUserId: apoc.CYPHER.runFirstColumn("RETURN $cypherParams.currentUserId AS cypherParamsUserId", {this: user, cypherParams: $cypherParams, strArg: "Neo4j", strInputArg: $1_strInputArg}, false)} AS `user` ---- diff --git a/core/src/test/resources/filter-tests.adoc b/core/src/test/resources/filter-tests.adoc index fd883206..28860110 100644 --- a/core/src/test/resources/filter-tests.adoc +++ b/core/src/test/resources/filter-tests.adoc @@ -467,7 +467,6 @@ RETURN person { } ---- - .Cypher [source,cypher] ---- @@ -542,7 +541,6 @@ RETURN person { { } ---- - .Cypher [source,cypher] ---- @@ -2036,6 +2034,7 @@ RETURN person { ''' === Filter string _matches_ + .GraphQL-Query [source,graphql] ---- diff --git a/core/src/test/resources/tck-test-files/cypher/advanced-filtering.adoc b/core/src/test/resources/tck-test-files/cypher/advanced-filtering.adoc index 09ec0e07..a966411a 100644 --- a/core/src/test/resources/tck-test-files/cypher/advanced-filtering.adoc +++ b/core/src/test/resources/tck-test-files/cypher/advanced-filtering.adoc @@ -113,7 +113,6 @@ RETURN movie { } ---- - .Expected Cypher params [source,json] ---- @@ -378,7 +377,6 @@ RETURN movie { } ---- - .Expected Cypher output [source,cypher] ---- @@ -680,7 +678,6 @@ RETURN movie { } ---- - .Expected Cypher output [source,cypher] ---- diff --git a/core/src/test/resources/tck-test-files/cypher/directives/ignore.adoc b/core/src/test/resources/tck-test-files/cypher/directives/ignore.adoc index 3143fd66..fb0b9d71 100644 --- a/core/src/test/resources/tck-test-files/cypher/directives/ignore.adoc +++ b/core/src/test/resources/tck-test-files/cypher/directives/ignore.adoc @@ -4,7 +4,6 @@ Tests that the @ignore directive works as expected. - == Inputs [source,graphql,schema=true] diff --git a/core/src/test/resources/tck-test-files/cypher/pagination.adoc b/core/src/test/resources/tck-test-files/cypher/pagination.adoc index f3047b43..f99b82ab 100644 --- a/core/src/test/resources/tck-test-files/cypher/pagination.adoc +++ b/core/src/test/resources/tck-test-files/cypher/pagination.adoc @@ -4,7 +4,6 @@ Tests for queries including reserved arguments `skip` and `limit`. - == Inputs [source,graphql,schema=true] @@ -27,7 +26,6 @@ input ActorOptions { } ---- - == Configuration .Configuration diff --git a/core/src/test/resources/tck-test-files/cypher/sort.adoc b/core/src/test/resources/tck-test-files/cypher/sort.adoc index dde38908..20f050b0 100644 --- a/core/src/test/resources/tck-test-files/cypher/sort.adoc +++ b/core/src/test/resources/tck-test-files/cypher/sort.adoc @@ -4,7 +4,6 @@ Tests for queries including reserved arguments `skip` and `limit`. - == Inputs [source,graphql,schema=true] diff --git a/core/src/test/resources/tck-test-files/schema/directives/ignore.adoc b/core/src/test/resources/tck-test-files/schema/directives/ignore.adoc index 9d6af3ee..a4c25cad 100644 --- a/core/src/test/resources/tck-test-files/schema/directives/ignore.adoc +++ b/core/src/test/resources/tck-test-files/schema/directives/ignore.adoc @@ -4,7 +4,6 @@ Tests that the @ignore directive works as expected. - == Inputs [source,graphql,schema=true] diff --git a/examples/dgs-spring-boot/src/main/resources/application.yaml b/examples/dgs-spring-boot/src/main/resources/application.yaml index 10bc1fdd..7383c5f9 100644 --- a/examples/dgs-spring-boot/src/main/resources/application.yaml +++ b/examples/dgs-spring-boot/src/main/resources/application.yaml @@ -5,6 +5,6 @@ org: authentication: username: movies password: movies - config : - encrypted : true + config: + encrypted: true database: movies diff --git a/examples/dgs-spring-boot/src/main/resources/schema/schema.graphqls b/examples/dgs-spring-boot/src/main/resources/schema/schema.graphqls index ccc8656d..5588fdb7 100644 --- a/examples/dgs-spring-boot/src/main/resources/schema/schema.graphqls +++ b/examples/dgs-spring-boot/src/main/resources/schema/schema.graphqls @@ -1,12 +1,12 @@ extend type Movie { - bar: String @ignore - javaData: [JavaData!] @ignore + bar: String @ignore + javaData: [JavaData!] @ignore } type JavaData { - name: String + name: String } extend type Query { - other: String + other: String } diff --git a/examples/graphql-kotlin-spring-boot/src/main/kotlin/org/neo4j/graphql/examples/graphqlspringboot/config/GraphQLConfiguration.kt b/examples/graphql-kotlin-spring-boot/src/main/kotlin/org/neo4j/graphql/examples/graphqlspringboot/config/GraphQLConfiguration.kt index 83869930..72382cf5 100644 --- a/examples/graphql-kotlin-spring-boot/src/main/kotlin/org/neo4j/graphql/examples/graphqlspringboot/config/GraphQLConfiguration.kt +++ b/examples/graphql-kotlin-spring-boot/src/main/kotlin/org/neo4j/graphql/examples/graphqlspringboot/config/GraphQLConfiguration.kt @@ -1,9 +1,9 @@ package org.neo4j.graphql.examples.graphqlspringboot.config +import com.expediagroup.graphql.generator.SchemaGenerator import com.expediagroup.graphql.generator.SchemaGeneratorConfig import com.expediagroup.graphql.generator.TopLevelObject import com.expediagroup.graphql.generator.extensions.print -import com.expediagroup.graphql.generator.SchemaGenerator import com.expediagroup.graphql.server.operations.Mutation import com.expediagroup.graphql.server.operations.Query import com.expediagroup.graphql.server.operations.Subscription @@ -35,8 +35,8 @@ open class GraphQLConfiguration { */ @Bean open fun neo4jSchema( - @Value("classpath:schema.graphql") graphQl: Resource, - @Autowired(required = false) dataFetchingInterceptor: DataFetchingInterceptor + @Value("classpath:schema.graphql") graphQl: Resource, + @Autowired(required = false) dataFetchingInterceptor: DataFetchingInterceptor ): GraphQLSchema { val schema = graphQl.inputStream.bufferedReader().use { it.readText() } return SchemaBuilder.buildSchema(schema, SchemaConfig(), dataFetchingInterceptor) @@ -47,17 +47,17 @@ open class GraphQLConfiguration { */ @Bean open fun springSchema( - queries: Optional>, - mutations: Optional>, - subscriptions: Optional>, - schemaConfig: SchemaGeneratorConfig + queries: Optional>, + mutations: Optional>, + subscriptions: Optional>, + schemaConfig: SchemaGeneratorConfig ): GraphQLSchema { val generator = SchemaGenerator(schemaConfig) return generator.use { it.generateSchema( - queries = queries.orElse(emptyList()).toTopLevelObjects(), - mutations = mutations.orElse(emptyList()).toTopLevelObjects(), - subscriptions = subscriptions.orElse(emptyList()).toTopLevelObjects() + queries = queries.orElse(emptyList()).toTopLevelObjects(), + mutations = mutations.orElse(emptyList()).toTopLevelObjects(), + subscriptions = subscriptions.orElse(emptyList()).toTopLevelObjects() ) } } diff --git a/examples/graphql-kotlin-spring-boot/src/test/kotlin/org/neo4j/graphql/examples/graphqlspringboot/controller/QueriesIT.kt b/examples/graphql-kotlin-spring-boot/src/test/kotlin/org/neo4j/graphql/examples/graphqlspringboot/controller/QueriesIT.kt index dbd73639..22d02bad 100644 --- a/examples/graphql-kotlin-spring-boot/src/test/kotlin/org/neo4j/graphql/examples/graphqlspringboot/controller/QueriesIT.kt +++ b/examples/graphql-kotlin-spring-boot/src/test/kotlin/org/neo4j/graphql/examples/graphqlspringboot/controller/QueriesIT.kt @@ -117,9 +117,9 @@ internal class QueriesIT( open class Config { @Bean - open fun neo4jConnectionDetails() = object :Neo4jConnectionDetails { + open fun neo4jConnectionDetails() = object : Neo4jConnectionDetails { override fun getUri(): URI = URI.create(neo4jServer.boltUrl) - override fun getAuthToken(): AuthToken = AuthTokens.basic("neo4j",neo4jServer.adminPassword) + override fun getAuthToken(): AuthToken = AuthTokens.basic("neo4j", neo4jServer.adminPassword) } } diff --git a/examples/graphql-spring-boot/src/main/resources/application.yaml b/examples/graphql-spring-boot/src/main/resources/application.yaml index 36ad287b..f52c7e44 100644 --- a/examples/graphql-spring-boot/src/main/resources/application.yaml +++ b/examples/graphql-spring-boot/src/main/resources/application.yaml @@ -1,17 +1,17 @@ -org : - neo4j : - driver : - uri : bolt://demo.neo4jlabs.com:7687 - authentication : - username : movies - password : movies - config : - encrypted : true -database : movies -spring : - graphql : - schema : - printer : - enabled : true - graphiql : - enabled : true +org: + neo4j: + driver: + uri: bolt://demo.neo4jlabs.com:7687 + authentication: + username: movies + password: movies + config: + encrypted: true +database: movies +spring: + graphql: + schema: + printer: + enabled: true + graphiql: + enabled: true diff --git a/examples/graphql-spring-boot/src/main/resources/graphql/schema.graphqls b/examples/graphql-spring-boot/src/main/resources/graphql/schema.graphqls index ccc8656d..5588fdb7 100644 --- a/examples/graphql-spring-boot/src/main/resources/graphql/schema.graphqls +++ b/examples/graphql-spring-boot/src/main/resources/graphql/schema.graphqls @@ -1,12 +1,12 @@ extend type Movie { - bar: String @ignore - javaData: [JavaData!] @ignore + bar: String @ignore + javaData: [JavaData!] @ignore } type JavaData { - name: String + name: String } extend type Query { - other: String + other: String } diff --git a/neo4j-graphql-augmented-schema-generator-maven-plugin/src/main/kotlin/org/neo4j/graphql/augmented_schema_generator/plugin/AugmentedSchemaGeneratorMojo.kt b/neo4j-graphql-augmented-schema-generator-maven-plugin/src/main/kotlin/org/neo4j/graphql/augmented_schema_generator/plugin/AugmentedSchemaGeneratorMojo.kt index 1ce1cfda..d66d5ae6 100644 --- a/neo4j-graphql-augmented-schema-generator-maven-plugin/src/main/kotlin/org/neo4j/graphql/augmented_schema_generator/plugin/AugmentedSchemaGeneratorMojo.kt +++ b/neo4j-graphql-augmented-schema-generator-maven-plugin/src/main/kotlin/org/neo4j/graphql/augmented_schema_generator/plugin/AugmentedSchemaGeneratorMojo.kt @@ -46,12 +46,13 @@ class AugmentedSchemaGeneratorMojo : AbstractMojo() { @Throws(MojoExecutionException::class) override fun execute() { val includedFiles: Array = fileSetManager.getIncludedFiles(fileset) - val schemaPrinter = SchemaPrinter(SchemaPrinter.Options - .defaultOptions() - .includeDirectives(true) - .includeScalarTypes(true) - .includeSchemaDefinition(true) - .includeIntrospectionTypes(false) + val schemaPrinter = SchemaPrinter( + SchemaPrinter.Options + .defaultOptions() + .includeDirectives(true) + .includeScalarTypes(true) + .includeSchemaDefinition(true) + .includeIntrospectionTypes(false) ) includedFiles.forEach { file -> diff --git a/readme.adoc b/readme.adoc index bd29b4ae..32ffb23f 100644 --- a/readme.adoc +++ b/readme.adoc @@ -4,7 +4,9 @@ :toclevels: 1 :toc-title: Quick Links -NOTE: This is a https://neo4j.com/labs/[Neo4j Labs Project^], it is not supported by Neo4j engineering or support and developed on an best-effort basis. It is also not functionally equivalent to the https://github.com/neo4j/graphql[neo4j-graphql (Javascript) library and API^] even as we try to stay close to it. Please use the official library for a JavaScript based GraphQL Middleware for production use-cases. +NOTE: This is a https://neo4j.com/labs/[Neo4j Labs Project^], it is not supported by Neo4j engineering or support and developed on an best-effort basis. +It is also not functionally equivalent to the https://github.com/neo4j/graphql[neo4j-graphql (Javascript) library and API^] even as we try to stay close to it. +Please use the official library for a JavaScript based GraphQL Middleware for production use-cases. This is a https://graphql.org[GraphQL] to https://neo4j.com/developer/cypher[Cypher] transpiler written in Kotlin. @@ -27,13 +29,13 @@ NOTE: All the <> are listed and explained below, mo For complex examples take a look at our link:examples/readme.adoc[example projects] - == API compatibility to @neo4j/graphql Since the javascript pendant of this library (neo4j-graphql-js) has majored into a neo4j product, we want to migrate our augmented schema, to match as much as possible to the one of the https://github.com/neo4j/graphql[`@neo4j/graphql`]. Therefore, we created a https://github.com/neo4j-graphql/neo4j-graphql-java/issues?q=label%3AAPI-Alignment[list of issues to track progress]. -We will try to make the migration as smooth as possible. For this purpose we will support the old, and the new way of schema augmentation until the next major release. +We will try to make the migration as smooth as possible. +For this purpose we will support the old, and the new way of schema augmentation until the next major release. To already test the new features, you can enable them via link:core/src/main/kotlin/org/neo4j/graphql/SchemaConfig.kt[some setting in the `SchemaConfig`] == FAQ @@ -258,7 +260,8 @@ This example doesn't handle introspection queries, but the one in the test direc * auto-generate query fields for all objects * @cypher directive for fields to compute field values, support arguments * @cypher directive for top level queries and mutations, supports arguments -* @cypher directives can have a `passThrough:true` argument, that gives sole responsibility for the nested query result for this field to your Cypher query. You will have to provide all data/structure required by client queries. +* @cypher directives can have a `passThrough:true` argument, that gives sole responsibility for the nested query result for this field to your Cypher query. +You will have to provide all data/structure required by client queries. Otherwise, we assume if you return object-types that you will return the appropriate nodes from your statement. * auto-generate mutation fields for all objects to create, update, delete * date(time) @@ -306,7 +309,9 @@ type Person { === Neo4j 5.x support -This library supports queries for both neo4j `4.x` and `5.x`. By default, the neo4j 5 dialect is enabled. The dialect can be changed via `QueryContext`. +This library supports queries for both neo4j `4.x` and `5.x`. +By default, the neo4j 5 dialect is enabled. +The dialect can be changed via `QueryContext`. .Example of changing the dialect via Translator [source,kotlin] @@ -339,7 +344,8 @@ The literal values are turned into Cypher query parameters. === Handle Relationships via @relation Directive on Schema Fields If you want to represent a relationship from the graph in GraphQL you have to add a `@relation` directive which contains the relationship-type and the direction. -The default direction for a relationship is 'OUT'. Other values are 'IN' and 'BOTH'. +The default direction for a relationship is 'OUT'. +Other values are 'IN' and 'BOTH'. So you can use different domain names in your GraphQL fields that are independent of your graph model. [source,graphql] @@ -563,8 +569,7 @@ You can also apply nested filter on relations, which use suffixes like `("",not, ==== Optimized Filters -If you encounter performance problems with the cypher queries generated for the filter, -you can activate an alternative algorithm using: +If you encounter performance problems with the cypher queries generated for the filter, you can activate an alternative algorithm using: [source,kotlin] ---- @@ -577,8 +582,7 @@ try { } ---- -If no query can be generated by the alternative algorithm, an `OptimizedQueryException` is thrown, -so that a fallback to the actual algorithm can be used. +If no query can be generated by the alternative algorithm, an `OptimizedQueryException` is thrown, so that a fallback to the actual algorithm can be used. link:core/src/test/resources/optimized-query-for-filter.adoc[Examples of the alternative algorithm] can be seen in the tests. @@ -616,7 +620,9 @@ It allows you, without code to *compute field values* using complex queries. You can also write your own, *custom top-level queries and mutations* using Cypher. -Arguments on the field are passed to the Cypher statement and can be used by name. They must not be prefixed by `$` since they are no longer parameters. Just use the same name as the field's argument. +Arguments on the field are passed to the Cypher statement and can be used by name. +They must not be prefixed by `$` since they are no longer parameters. +Just use the same name as the field's argument. The current node is passed to the statement as `this`. The statement should contain exactly one return expression without any alias. @@ -741,7 +747,8 @@ and the link:core/src/test/resources/custom-fields.adoc[Custom queries and mutat == Build time schema augmentation -Sometimes you need the possibility to generate the augmented schema at compile time. To achieve this, we provide a maven plugin which can be used as follows: +Sometimes you need the possibility to generate the augmented schema at compile time. +To achieve this, we provide a maven plugin which can be used as follows: [source,xml,subs="attributes,verbatim"] ---- @@ -773,6 +780,7 @@ Sometimes you need the possibility to generate the augmented schema at compile t ---- + <1> Use the same configuration as for your SchemaBuilder <2> Define the source schema for which you want to have an augmented schema generated