File: truncate.rb

package info (click to toggle)
ruby-gitlab-pg-query 2.0.4-3
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 15,584 kB
  • sloc: ansic: 143,939; ruby: 2,096; makefile: 4
file content (89 lines) | stat: -rw-r--r-- 3,925 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

module PgQuery
  class ParserResult
    PossibleTruncation = Struct.new(:location, :node_type, :length, :is_array)

    # Truncates the query string to be below the specified length, first trying to
    # omit less important parts of the query, and only then cutting off the end.
    def truncate(max_length) # rubocop:disable Metrics/CyclomaticComplexity
      output = deparse

      # Early exit if we're already below the max length
      return output if output.size <= max_length

      truncations = find_possible_truncations

      # Truncate the deepest possible truncation that is the longest first
      truncations.sort_by! { |t| [-t.location.size, -t.length] }

      tree = dup_tree
      truncations.each do |truncation|
        next if truncation.length < 3

        find_tree_location(tree, truncation.location) do |node, _k|
          dummy_column_ref = PgQuery::Node.new(column_ref: PgQuery::ColumnRef.new(fields: [PgQuery::Node.new(string: PgQuery::String.new(str: '…'))]))
          case truncation.node_type
          when :target_list
            res_target_name = '…' if node.is_a?(PgQuery::UpdateStmt) || node.is_a?(PgQuery::OnConflictClause)
            node.target_list.replace(
              [
                PgQuery::Node.new(res_target: PgQuery::ResTarget.new(name: res_target_name, val: dummy_column_ref))
              ]
            )
          when :where_clause
            node.where_clause = dummy_column_ref
          when :ctequery
            node.ctequery = PgQuery::Node.new(select_stmt: PgQuery::SelectStmt.new(where_clause: dummy_column_ref, op: :SETOP_NONE))
          when :cols
            node.cols.replace([PgQuery::Node.from(PgQuery::ResTarget.new(name: '…'))]) if node.is_a?(PgQuery::InsertStmt)
          else
            raise ArgumentError, format('Unexpected truncation node type: %s', truncation.node_type)
          end
        end

        output = PgQuery.deparse(tree).gsub('SELECT WHERE "…"', '...').gsub('"…"', '...')
        return output if output.size <= max_length
      end

      # We couldn't do a proper smart truncation, so we need a hard cut-off
      output[0..max_length - 4] + '...'
    end

    private

    def find_possible_truncations # rubocop:disable Metrics/CyclomaticComplexity
      truncations = []

      treewalker! @tree do |node, k, v, location|
        case k
        when :target_list
          next unless node.is_a?(PgQuery::SelectStmt) || node.is_a?(PgQuery::UpdateStmt) || node.is_a?(PgQuery::OnConflictClause)
          length = PgQuery.deparse_stmt(PgQuery::SelectStmt.new(k => v.to_a, op: :SETOP_NONE)).size - 7 # 'SELECT '.size
          truncations << PossibleTruncation.new(location, :target_list, length, true)
        when :where_clause
          next unless node.is_a?(PgQuery::SelectStmt) || node.is_a?(PgQuery::UpdateStmt) || node.is_a?(PgQuery::DeleteStmt) ||
                      node.is_a?(PgQuery::CopyStmt) || node.is_a?(PgQuery::IndexStmt) || node.is_a?(PgQuery::RuleStmt) ||
                      node.is_a?(PgQuery::InferClause) || node.is_a?(PgQuery::OnConflictClause)

          length = PgQuery.deparse_expr(v).size
          truncations << PossibleTruncation.new(location, :where_clause, length, false)
        when :ctequery
          next unless node.is_a?(PgQuery::CommonTableExpr)
          length = PgQuery.deparse_stmt(v[v.node.to_s]).size
          truncations << PossibleTruncation.new(location, :ctequery, length, false)
        when :cols
          next unless node.is_a?(PgQuery::InsertStmt)
          length = PgQuery.deparse_stmt(
            PgQuery::InsertStmt.new(
              relation: PgQuery::RangeVar.new(relname: 'x', inh: true),
              cols: v.to_a
            )
          ).size - 31 # "INSERT INTO x () DEFAULT VALUES".size
          truncations << PossibleTruncation.new(location, :cols, length, true)
        end
      end

      truncations
    end
  end
end