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
|