diff options
Diffstat (limited to 'lib/gitlab/graphql')
-rw-r--r-- | lib/gitlab/graphql/authorize/authorize_resource.rb | 4 | ||||
-rw-r--r-- | lib/gitlab/graphql/global_id_compatibility.rb | 20 | ||||
-rw-r--r-- | lib/gitlab/graphql/markdown_field.rb | 8 | ||||
-rw-r--r-- | lib/gitlab/graphql/markdown_field/resolver.rb | 22 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/connection.rb | 3 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/last_items.rb | 57 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/order_info.rb | 16 | ||||
-rw-r--r-- | lib/gitlab/graphql/query_analyzers/logger_analyzer.rb | 11 |
8 files changed, 110 insertions, 31 deletions
diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb index 27673e5c27a..c70127553fd 100644 --- a/lib/gitlab/graphql/authorize/authorize_resource.rb +++ b/lib/gitlab/graphql/authorize/authorize_resource.rb @@ -29,8 +29,8 @@ module Gitlab raise NotImplementedError, "Implement #find_object in #{self.class.name}" end - def authorized_find!(*args) - object = Graphql::Lazy.force(find_object(*args)) + def authorized_find!(*args, **kwargs) + object = Graphql::Lazy.force(find_object(*args, **kwargs)) authorize!(object) diff --git a/lib/gitlab/graphql/global_id_compatibility.rb b/lib/gitlab/graphql/global_id_compatibility.rb new file mode 100644 index 00000000000..a96e4c4b976 --- /dev/null +++ b/lib/gitlab/graphql/global_id_compatibility.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module GlobalIDCompatibility + # TODO: remove this module once the compatibility layer is no longer needed. + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + def coerce_global_id_arguments!(args) + global_id_arguments = self.class.arguments.values.select do |arg| + arg.type.is_a?(Class) && arg.type <= ::Types::GlobalIDType + end + + global_id_arguments.each do |arg| + k = arg.keyword + args[k] &&= arg.type.coerce_isolated_input(args[k]) + end + end + end + end +end diff --git a/lib/gitlab/graphql/markdown_field.rb b/lib/gitlab/graphql/markdown_field.rb index 7be6810f7ba..0b5bde8d8d9 100644 --- a/lib/gitlab/graphql/markdown_field.rb +++ b/lib/gitlab/graphql/markdown_field.rb @@ -12,13 +12,19 @@ module Gitlab end method_name = kwargs.delete(:method) || name.to_s.sub(/_html$/, '') - kwargs[:resolve] = Gitlab::Graphql::MarkdownField::Resolver.new(method_name.to_sym).proc + resolver_method = "#{name}_resolver".to_sym + kwargs[:resolver_method] = resolver_method kwargs[:description] ||= "The GitLab Flavored Markdown rendering of `#{method_name}`" # Adding complexity to rendered notes since that could cause queries. kwargs[:complexity] ||= 5 field name, GraphQL::STRING_TYPE, **kwargs + + define_method resolver_method do + # We need to `dup` the context so the MarkdownHelper doesn't modify it + ::MarkupHelper.markdown_field(object, method_name.to_sym, context.to_h.dup) + end end end end diff --git a/lib/gitlab/graphql/markdown_field/resolver.rb b/lib/gitlab/graphql/markdown_field/resolver.rb deleted file mode 100644 index 11a01b95ad1..00000000000 --- a/lib/gitlab/graphql/markdown_field/resolver.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Graphql - module MarkdownField - class Resolver - attr_reader :method_name - - def initialize(method_name) - @method_name = method_name - end - - def proc - -> (object, _args, ctx) do - # We need to `dup` the context so the MarkdownHelper doesn't modify it - ::MarkupHelper.markdown_field(object, method_name, ctx.to_h.dup) - end - end - end - end - end -end diff --git a/lib/gitlab/graphql/pagination/keyset/connection.rb b/lib/gitlab/graphql/pagination/keyset/connection.rb index 17cd22d38ad..252f6371765 100644 --- a/lib/gitlab/graphql/pagination/keyset/connection.rb +++ b/lib/gitlab/graphql/pagination/keyset/connection.rb @@ -110,8 +110,7 @@ module Gitlab end if last - # grab one more than we need - paginated_nodes = sliced_nodes.last(limit_value + 1) + paginated_nodes = LastItems.take_items(sliced_nodes, limit_value + 1) # there is an extra node, so there is a previous page @has_previous_page = paginated_nodes.count > limit_value diff --git a/lib/gitlab/graphql/pagination/keyset/last_items.rb b/lib/gitlab/graphql/pagination/keyset/last_items.rb new file mode 100644 index 00000000000..45bf15236c1 --- /dev/null +++ b/lib/gitlab/graphql/pagination/keyset/last_items.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module Pagination + module Keyset + # This class handles the last(N) ActiveRecord call even if a special ORDER BY configuration is present. + # For the last(N) call, ActiveRecord calls reverse_order, however for some cases it raises + # ActiveRecord::IrreversibleOrderError error. + class LastItems + # rubocop: disable CodeReuse/ActiveRecord + def self.take_items(scope, count) + if custom_order = lookup_custom_reverse_order(scope.order_values) + items = scope.reorder(*custom_order).first(count) # returns a single record when count is nil + items.is_a?(Array) ? items.reverse : items + else + scope.last(count) + end + end + # rubocop: enable CodeReuse/ActiveRecord + + # Detect special ordering and provide the reversed order + def self.lookup_custom_reverse_order(order_values) + if ordering_by_merged_at_and_mr_id_desc?(order_values) + [ + Gitlab::Database.nulls_first_order('merge_request_metrics.merged_at', 'ASC'), # reversing the order + MergeRequest.arel_table[:id].asc + ] + elsif ordering_by_merged_at_and_mr_id_asc?(order_values) + [ + Gitlab::Database.nulls_first_order('merge_request_metrics.merged_at', 'DESC'), + MergeRequest.arel_table[:id].asc + ] + end + end + + def self.ordering_by_merged_at_and_mr_id_desc?(order_values) + order_values.size == 2 && + order_values.first.to_s == Gitlab::Database.nulls_last_order('merge_request_metrics.merged_at', 'DESC') && + order_values.last.is_a?(Arel::Nodes::Descending) && + order_values.last.to_sql == MergeRequest.arel_table[:id].desc.to_sql + end + + def self.ordering_by_merged_at_and_mr_id_asc?(order_values) + order_values.size == 2 && + order_values.first.to_s == Gitlab::Database.nulls_last_order('merge_request_metrics.merged_at', 'ASC') && + order_values.last.is_a?(Arel::Nodes::Descending) && + order_values.last.to_sql == MergeRequest.arel_table[:id].desc.to_sql + end + + private_class_method :ordering_by_merged_at_and_mr_id_desc? + private_class_method :ordering_by_merged_at_and_mr_id_asc? + end + end + end + end +end diff --git a/lib/gitlab/graphql/pagination/keyset/order_info.rb b/lib/gitlab/graphql/pagination/keyset/order_info.rb index f54695ddb9a..f3ce3a10703 100644 --- a/lib/gitlab/graphql/pagination/keyset/order_info.rb +++ b/lib/gitlab/graphql/pagination/keyset/order_info.rb @@ -94,6 +94,10 @@ module Gitlab [order_value.expr.expressions[0].name.to_s, order_value.direction, order_value.expr] elsif ordering_by_similarity?(order_value) ['similarity', order_value.direction, order_value.expr] + elsif ordering_by_case?(order_value) + ['case_order_value', order_value.direction, order_value.expr] + elsif ordering_by_array_position?(order_value) + ['array_position', order_value.direction, order_value.expr] else [order_value.expr.name, order_value.direction, nil] end @@ -104,9 +108,19 @@ module Gitlab order_value.expr.is_a?(Arel::Nodes::NamedFunction) && order_value.expr&.name&.downcase == 'lower' end + # determine if ordering using ARRAY_POSITION, eg. "ORDER BY ARRAY_POSITION(Array[4,3,1,2]::smallint, state)" + def ordering_by_array_position?(order_value) + order_value.expr.is_a?(Arel::Nodes::NamedFunction) && order_value.expr&.name&.downcase == 'array_position' + end + # determine if ordering using SIMILARITY scoring based on Gitlab::Database::SimilarityScore def ordering_by_similarity?(order_value) - order_value.to_sql.match?(/SIMILARITY\(.+\*/) + Gitlab::Database::SimilarityScore.order_by_similarity?(order_value) + end + + # determine if ordering using CASE + def ordering_by_case?(order_value) + order_value.expr.is_a?(Arel::Nodes::Case) end end end diff --git a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb index 6b6bb72eb31..1285365376f 100644 --- a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb +++ b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb @@ -6,6 +6,8 @@ module Gitlab class LoggerAnalyzer COMPLEXITY_ANALYZER = GraphQL::Analysis::QueryComplexity.new { |query, complexity_value| complexity_value } DEPTH_ANALYZER = GraphQL::Analysis::QueryDepth.new { |query, depth_value| depth_value } + FIELD_USAGE_ANALYZER = GraphQL::Analysis::FieldUsage.new { |query, used_fields, used_deprecated_fields| [used_fields, used_deprecated_fields] } + ALL_ANALYZERS = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER, FIELD_USAGE_ANALYZER].freeze def analyze?(query) Feature.enabled?(:graphql_logging, default_enabled: true) @@ -23,18 +25,21 @@ module Gitlab end def call(memo, visit_type, irep_node) - memo + RequestStore.store[:graphql_logs] = memo end def final_value(memo) return if memo.nil? - analyzers = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER] - complexity, depth = GraphQL::Analysis.analyze_query(memo[:query], analyzers) + complexity, depth, field_usages = GraphQL::Analysis.analyze_query(memo[:query], ALL_ANALYZERS) memo[:depth] = depth memo[:complexity] = complexity + # This duration is not the execution time of the + # query but the execution time of the analyzer. memo[:duration_s] = duration(memo[:time_started]).round(1) + memo[:used_fields] = field_usages.first + memo[:used_deprecated_fields] = field_usages.second GraphqlLogger.info(memo.except!(:time_started, :query)) rescue => e |