# frozen-string-literal: true module Sequel # The +ASTTransformer+ class is designed to handle the abstract syntax trees # that Sequel uses internally and produce modified copies of them. By itself # it only produces a straight copy. It's designed to be subclassed and have # subclasses returned modified copies of the specific nodes that need to # be modified. class ASTTransformer # Return +obj+ or a potentially transformed version of it. def transform(obj) v(obj) end private # Recursive version that handles all of Sequel's internal object types # and produces copies of them. def v(o) case o when Symbol, Numeric, String, Class, TrueClass, FalseClass, NilClass o when Array o.map{|x| v(x)} when Hash h = {} o.each{|k, val| h[v(k)] = v(val)} h when SQL::NumericExpression if o.op == :extract o.class.new(o.op, o.args[0], v(o.args[1])) else o.class.new(o.op, *v(o.args)) end when SQL::ComplexExpression o.class.new(o.op, *v(o.args)) when SQL::Identifier SQL::Identifier.new(v(o.value)) when SQL::QualifiedIdentifier SQL::QualifiedIdentifier.new(v(o.table), v(o.column)) when SQL::OrderedExpression SQL::OrderedExpression.new(v(o.expression), o.descending, :nulls=>o.nulls) when SQL::AliasedExpression SQL::AliasedExpression.new(v(o.expression), o.alias, o.columns) when SQL::CaseExpression args = [v(o.conditions), v(o.default)] args << v(o.expression) if o.expression? SQL::CaseExpression.new(*args) when SQL::Cast SQL::Cast.new(v(o.expr), o.type) when SQL::Function h = {} o.opts.each do |k, val| h[k] = v(val) end SQL::Function.new!(o.name, v(o.args), h) when SQL::Subscript SQL::Subscript.new(v(o.expression), v(o.sub)) when SQL::Window opts = o.opts.dup opts[:partition] = v(opts[:partition]) if opts[:partition] opts[:order] = v(opts[:order]) if opts[:order] SQL::Window.new(opts) when SQL::PlaceholderLiteralString args = if o.args.is_a?(Hash) h = {} o.args.each{|k,val| h[k] = v(val)} h else v(o.args) end SQL::PlaceholderLiteralString.new(o.str, args, o.parens) when SQL::JoinOnClause SQL::JoinOnClause.new(v(o.on), o.join_type, v(o.table_expr)) when SQL::JoinUsingClause SQL::JoinUsingClause.new(v(o.using), o.join_type, v(o.table_expr)) when SQL::JoinClause SQL::JoinClause.new(o.join_type, v(o.table_expr)) when SQL::DelayedEvaluation SQL::DelayedEvaluation.new(lambda{|ds| v(o.call(ds))}) when SQL::Wrapper SQL::Wrapper.new(v(o.value)) when SQL::Expression if o.respond_to?(:sequel_ast_transform) o.sequel_ast_transform(method(:v)) else o end else o end end end # Handles qualifying existing datasets, so that unqualified columns # in the dataset are qualified with a given table name. class Qualifier < ASTTransformer # Set the table used to qualify unqualified columns def initialize(table) @table = table end private # Turn SQL::Identifiers and symbols that aren't implicitly # qualified into SQL::QualifiedIdentifiers. For symbols that # are not implicitly qualified by are implicitly aliased, return an # SQL::AliasedExpressions with a qualified version of the symbol. def v(o) case o when Symbol t, column, aliaz = Sequel.split_symbol(o) if t o elsif aliaz SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(@table, SQL::Identifier.new(column)), aliaz) else SQL::QualifiedIdentifier.new(@table, o) end when SQL::Identifier SQL::QualifiedIdentifier.new(@table, o) when SQL::QualifiedIdentifier, SQL::JoinClause # Return these directly, so we don't accidentally qualify symbols in them. o else super end end end end