File: truncate.rb

package info (click to toggle)
ruby-pg-query 5.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 18,248 kB
  • sloc: ansic: 149,767; ruby: 865; makefile: 3
file content (135 lines) | stat: -rw-r--r-- 5,269 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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

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(sval: '…'))]))
          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 :values_lists
            node.values_lists.replace(
              [
                PgQuery::Node.new(list: PgQuery::List.new(items: [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 = if node.is_a?(PgQuery::SelectStmt)
                     select_target_list_len(v)
                   else # UpdateStmt / OnConflictClause
                     update_target_list_len(v)
                   end
          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 :values_lists
          length = select_values_lists_len(v)
          truncations << PossibleTruncation.new(location, :values_lists, 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 = cols_len(v)
          truncations << PossibleTruncation.new(location, :cols, length, true)
        end
      end

      truncations
    end

    def select_target_list_len(target_list)
      deparsed_len = PgQuery.deparse_stmt(
        PgQuery::SelectStmt.new(
          target_list: target_list.to_a, op: :SETOP_NONE
        )
      ).size
      deparsed_len - 7 # 'SELECT '.size
    end

    def select_values_lists_len(values_lists)
      deparsed_len = PgQuery.deparse_stmt(
        PgQuery::SelectStmt.new(
          values_lists: values_lists.to_a, op: :SETOP_NONE
        )
      ).size
      deparsed_len - 7 # 'SELECT '.size
    end

    def update_target_list_len(target_list)
      deparsed_len = PgQuery.deparse_stmt(
        PgQuery::UpdateStmt.new(
          target_list: target_list.to_a,
          relation: PgQuery::RangeVar.new(relname: 'x', inh: true)
        )
      ).size
      deparsed_len - 13 # 'UPDATE x SET '.size
    end

    def cols_len(cols)
      deparsed_len = PgQuery.deparse_stmt(
        PgQuery::InsertStmt.new(
          relation: PgQuery::RangeVar.new(relname: 'x', inh: true),
          cols: cols.to_a
        )
      ).size
      deparsed_len - 31 # "INSERT INTO x () DEFAULT VALUES".size
    end
  end
end