# Encode for: # # `ORDER BY expression [ASC | DESC | USING operator] [NULLS FIRST | NULLS LAST];` # # Current implementation: # # [x] Multiple Order by clauses # [x] ASC/DESC # [x] NULLS FIRST / NULLS LAST # [ ] NOT IMPLEMENTED: USING OPERATOR module Clear::SQL::Query::OrderBy record Record, op : String, dir : Symbol, nulls : Symbol? macro included getter order_bys : Array(Clear::SQL::Query::OrderBy::Record) = [] of Clear::SQL::Query::OrderBy::Record end # Remove all order by clauses def clear_order_bys @order_bys.clear change! end # :nodoc: private def sanitize_direction(x) case x when :asc, :desc x else raise QueryBuildingError.new("Unknown direction for ORDER_BY: #{x.to_s.upcase}") end end # :nodoc: private def sanitize_nulls(x) case x when nil, :nulls_last, :nulls_first x else raise QueryBuildingError.new("Unknown ORDER_BY ... NULLS directive: #{x.to_s.upcase}") end end # Flip over all order bys by switching the ASC direction to DESC and the NULLS FIRST to NULLS LAST # ``` # query = Clear::SQL.select.from("users").order_by(id: :desc, name: :asc, company: {:asc, :nulls_last}) # query.reverse_order_by # query.to_sql # SELECT * FROM users ORDER BY "id" ASC, "name" DESC, "company" DESC NULLS FIRST # ``` # # return `self` def reverse_order_by @order_bys = @order_bys.map { |rec| Record.new(rec.op, rec.dir == :desc ? :asc : :desc, rec.nulls.try { |n| n == :nulls_last ? :nulls_first : :nulls_last } ) } change! end # :nodoc: def order_by(x : Array(Record)) @order_bys = x change! end # Add multiple ORDER BY clause using a tuple: # # ``` # query = Clear::SQL.select.from("users").order_by(id: :desc, name: {:asc, :nulls_last}) # query.to_sql # > SELECT * FROM users ORDER BY "id" DESC, "name" ASC NULLS LAST # ``` # def order_by(**tuple) order_by(tuple) end # :ditto: def order_by(__tuple : NamedTuple) __tuple.each do |k, v| case v when Symbol, String order_by(k, v, nil) when Tuple # order_by(column: {:asc, :nulls_first}) order_by(k, v[0], v[1]) else raise "order_by with namedtuple must be called with value of the tuple as Symbol, String or Tuple describing direction and nulls directive" end end self end # Add one ORDER BY clause # ``` # query = Clear::SQL.select.from("users").order_by(:id, :desc, nulls_last) # query.to_sql # > SELECT * FROM users ORDER BY "id" DESC NULLS LAST # ``` def order_by(expression : Symbol, direction : Symbol = :asc, nulls : Symbol? = nil) @order_bys << Record.new(SQL.escape(expression.to_s), sanitize_direction(direction), sanitize_nulls(nulls)) change! end # :ditto: def order_by(expression : String, direction : Symbol = :asc, nulls : Symbol? = nil) @order_bys << Record.new(expression, sanitize_direction(direction), sanitize_nulls(nulls)) change! end # :nodoc: private def to_nulls_statement(symbol) case symbol when :nulls_first "NULLS FIRST" when :nulls_last "NULLS LAST" else nil end end # :nodoc: protected def print_order_bys return unless @order_bys.any? "ORDER BY " + @order_bys.map { |r| [r.op, r.dir.to_s.upcase, to_nulls_statement(r.nulls)].compact.join(" ") }.join(", ") end end