2
0
mirror of https://github.com/moebooru/moebooru synced 2025-08-22 01:47:48 +00:00
moebooru/lib/nagato.rb
2024-01-08 19:39:01 +09:00

186 lines
4.7 KiB
Ruby

# Nagato is a library that allows you to programatically build SQL queries.
module Nagato
# Represents a single subquery.
class Subquery
# === Parameters
# * :join<String>:: Can be either "and" or "or". All the conditions will be joined using this string.
def initialize(join = "and")
@join = join.upcase
@conditions = []
@condition_params = []
end
# Returns true if the subquery is empty.
def empty?
@conditions.empty?
end
# Returns an array of 1 or more elements, the first being a SQL fragment and the rest being placeholder parameters.
def conditions
if @conditions.empty?
[ "TRUE" ]
else
[ @conditions.join(" " + @join + " "), *@condition_params ]
end
end
# Creates a subquery (within the current subquery).
#
# === Parameters
# * :join<String>:: Can be either "and" or "or". This will be passed on to the generated subquery.
def subquery(join = "and")
subconditions = self.class.new(join)
yield(subconditions)
c = subconditions.conditions
@conditions << "(#{c[0]})"
@condition_params += c[1..-1]
end
# Adds a condition to the subquery. If the condition has placeholder parameters, you can pass them in directly in :params:.
#
# === Parameters
# * :sql<String>:: A SQL fragment.
# * :params<Object>:: A list of object to be used as the placeholder parameters.
def add(sql, *params)
@conditions << sql
@condition_params += params
end
# A special case in which there's only one parameter. If the parameter is nil, then don't add the condition.
#
# === Parameters
# * :sql<String>:: A SQL fragment.
# * :param<Object>:: A placeholder parameter.
def add_unless_blank(sql, param)
unless param.nil? || param == ""
@conditions << sql
@condition_params << param
end
end
end
class Builder
attr_reader :order, :limit, :offset
# Constructs a new Builder object. You must use it in block form.
#
# Example:
#
# n = Nagato::Builder.new do |builder, cond|
# builder.get("posts.id")
# builder.get("posts.rating")
# builder.rjoin("posts_tags ON posts_tags.post_id = posts.id")
# cond.add_unless_blank "posts.rating = ?", params[:rating]
# cond.subquery do |c1|
# c1.add "posts.user_id is null"
# c1.add "posts.user_id = 1"
# end
# end
#
# Post.find(:all, n.to_hash)
def initialize
@select = []
@joins = []
@subquery = Subquery.new("and")
@order = nil
@offset = nil
@limit = nil
yield(self, @subquery)
end
# Defines a new join.
#
# Example:
#
# cond.join "posts_tags ON posts_tags.post_id = posts.id"
def join(sql)
@joins << "JOIN " + sql
end
# Defines a new left join.
#
# Example:
#
# cond.ljoin "posts_tags ON posts_tags.post_id = posts.id"
def ljoin(sql)
@joins << "LEFT JOIN " + sql
end
# Defines a new right join.
#
# Example:
#
# cond.rjoin "posts_tags ON posts_tags.post_id = posts.id"
def rjoin(sql)
@joins << "RIGHT JOIN " + sql
end
# Defines the select list.
#
# === Parameters
# * :fields<String, Array>: the fields to select
def get(fields)
if fields.is_a?(String)
@select << fields
elsif fields.is_a?(Array)
@select += fields
else
raise TypeError
end
end
# Sets the ordering.
#
# === Parameters
# * :sql<String>:: A SQL fragment defining the ordering
def order(sql)
@order = sql
end
# Sets the limit.
#
# === Parameters
# * :amount<Integer>:: The amount
def limit(amount)
@limit = amount.to_i
end
# Sets the offset.
#
# === Parameters
# * :amount<Integer>:: The amount
def offset(amount)
@offset = amount.to_i
end
# Return the conditions (as an array suitable for usage with ActiveRecord)
def conditions
@subquery.conditions
end
# Returns the joins (as an array suitable for usage with ActiveRecord)
def joins
@joins.join(" ")
end
# Converts the SQL fragment as a hash (suitable for usage with ActiveRecord)
def to_hash
hash = {}
hash[:conditions] = conditions
hash[:joins] = joins unless @joins.empty?
hash[:order] = @order if @order
hash[:limit] = @limit if @limit
hash[:offset] = @offset if @offset
hash[:select] = @select if @select.any?
hash
end
end
def find(model, &block)
model.find(:all, Builder.new(&block).to_hash)
end
module_function :find
end