Module: Fast::SQL

Defined in:
lib/fast/sql.rb,
lib/fast/sql/rewriter.rb

Overview

This module contains methods to parse SQL statements and rewrite them. It uses PGQuery to parse the SQL statements. It uses Parser to rewrite the SQL statements. It uses Parser::Source::Map to map the AST nodes to the SQL tokens.

Examples:

Fast::SQL.parse("select 1")
=> s(:select_stmt, s(:target_list, ...

See Also:

Defined Under Namespace

Classes: Node, Rewriter, SourceBuffer

Class Method Summary collapse

Class Method Details

.clean_structure(stmt) ⇒ Hash

Clean up the hash structure returned by PgQuery

Returns:

  • (Hash)

    the hash representation of the sql statement



130
131
132
133
134
135
136
137
138
139
# File 'lib/fast/sql.rb', line 130

def clean_structure(stmt)
  res_hash = stmt.map do |key, value|
    value = clean_structure(value) if value.is_a?(Hash)
    value = value.map(&Fast::SQL.method(:clean_structure)) if value.is_a?(Array)
    value = nil if [{}, [], "", :SETOP_NONE, :LIMIT_OPTION_DEFAULT, false].include?(value)
    key = key.to_s.tr('-','_').to_sym
    [key, value]
  end
  res_hash.to_h.compact
end

.parse(statement, buffer_name: "(sql)") ⇒ Object

Parses SQL statements Using PGQuery

See Also:

  • sql_to_h


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/fast/sql.rb', line 110

def parse(statement, buffer_name: "(sql)")
  return [] if statement.nil?
  source_buffer = SQL::SourceBuffer.new(buffer_name, source: statement)
  tree = PgQuery.parse(statement).tree
  stmts = tree.stmts.map do |stmt|
    v = clean_structure(stmt.stmt.to_h)
    inner_stmt = statement[stmt.stmt_location, stmt.stmt_len]
    first, *, last = source_buffer.tokens
    from = stmt.stmt_location
    to = from.zero? ? last.end : from + stmt.stmt_len
    expression = Parser::Source::Range.new(source_buffer, from, to)
    source_map = Parser::Source::Map.new(expression)
    sql_tree_to_ast(v, source_buffer: source_buffer, source_map: source_map)
  end.flatten
  stmts.one? ? stmts.first : stmts
end

.parse_file(file) ⇒ Object

Returns Fast::SQL::Node with the parsed content.

Returns:

  • Fast::SQL::Node with the parsed content



21
22
23
# File 'lib/fast/sql/rewriter.rb', line 21

def parse_file(file)
  parse(IO.read(file), buffer_name: file)
end

.replace(pattern, ast, &replacement) ⇒ Object

Returns string with the content updated in case the pattern matches.

Returns:

  • string with the content updated in case the pattern matches.

See Also:

  • Fast::SQLRewriter


6
7
8
# File 'lib/fast/sql/rewriter.rb', line 6

def replace(pattern, ast, &replacement)
  sql_rewriter_for(pattern, ast, &replacement).rewrite!
end

.replace_file(pattern, file, &replacement) ⇒ Object

Replace a SQL file with the given pattern. Use a replacement code block to change the content.

Returns:

  • nil in case does not update the file

  • true in case the file is updated

See Also:



30
31
32
33
34
35
36
37
# File 'lib/fast/sql/rewriter.rb', line 30

def replace_file(pattern, file, &replacement)
  original = IO.read(file)
  ast = parse_file(file)
  content = replace(pattern, ast, &replacement)
  if content != original
    File.open(file, 'w+') { |f| f.print content }
  end
end

.sql_rewriter_for(pattern, ast, &replacement) ⇒ Fast::SQL::Rewriter

Returns:

See Also:



12
13
14
15
16
17
18
# File 'lib/fast/sql/rewriter.rb', line 12

def sql_rewriter_for(pattern, ast, &replacement)
  rewriter = Rewriter.new
  rewriter.ast = ast
  rewriter.search = pattern
  rewriter.replacement = replacement
  rewriter
end

.sql_tree_to_ast(obj, source_buffer: nil, source_map: nil) ⇒ Array

Transform a sql tree into an AST. Populates the location of the AST nodes with the source map.

Returns:

  • (Array)

    the AST representation of the sql statement



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/fast/sql.rb', line 145

def sql_tree_to_ast(obj, source_buffer: nil, source_map: nil)
  recursive = -> (e) { sql_tree_to_ast(e, source_buffer: source_buffer, source_map: source_map.dup) }
  case obj
  when Array
    obj.map(&recursive).flatten.compact
  when Hash
    if (start = obj.delete(:location))
      if (token = source_buffer.tokens.find{|e|e.start == start})
        expression = Parser::Source::Range.new(source_buffer, token.start, token.end)
        source_map = Parser::Source::Map.new(expression)
      end
    end
    obj.map do |key, value|
      children  = [*recursive.call(value)]
      Node.new(key, children, location: source_map)
    end.compact
  else
    obj
  end
end