require "forwardable"
require "logger"
require_relative "sql/version.rb"
require_relative "sql/decoder.rb"
require_relative "sql/encoder.rb"
require_relative "sql/config.rb"
require_relative "sql/logging.rb"
require_relative "sql/connection.rb"
require_relative "sql/reflection.rb"
require_relative "sql/insert.rb"
module Simple
# The Simple::SQL module
module SQL
extend self
attr_accessor :logger
self.logger = Logger.new(STDERR)
# execute one or more sql statements. This method does not allow to pass in
# arguments - since the pg client does not support this - but it allows to
# run multiple sql statements separated by ";"
def exec(sql)
Logging.yield_logged sql do
connection.exec sql
end
end
# Runs a query, with optional arguments, and returns the result. If the SQL
# query returns rows with one column, this method returns an array of these
# values. Otherwise it returns an array of arrays.
#
# Example:
#
# - Simple::SQL.all("SELECT id FROM users") returns an array of id values
# - Simple::SQL.all("SELECT id, email FROM users") returns an array of
# arrays `[ , ]`.
#
# Simple::SQL.all "SELECT id, email FROM users" do |id, email|
# # do something
# end
def all(sql, *args, &block)
result = exec_logged(sql, *args)
decoder = Decoder.new(result)
enumerate(result, decoder, block)
end
# Runs a query and returns the first result row of a query.
#
# Examples:
#
# - Simple::SQL.ask "SELECT id FROM users WHERE email=$?", "foo@local"
# returns a number (or +nil+)
# - Simple::SQL.ask "SELECT id, email FROM users WHERE email=$?", "foo@local"
# returns an array [ , ] (or +nil+)
def ask(sql, *args)
catch(:ok) do
all(sql, *args) { |row| throw :ok, row }
nil
end
end
# Runs a query, with optional arguments, and returns the result as an
# array of Hashes.
#
# Example:
#
# - Simple::SQL.records("SELECT id, email FROM users") returns an array of
# hashes { id: id, email: email }
#
# Simple::SQL.records "SELECT id, email FROM users" do |record|
# # do something
# end
def records(sql, *args, into: Hash, &block)
result = exec_logged(sql, *args)
decoder = Decoder.new(result, :record, into: into)
enumerate(result, decoder, block)
end
# Runs a query and returns the first result row of a query as a Hash.
def record(sql, *args, into: Hash)
catch(:ok) do
records(sql, *args, into: into) { |row| throw :ok, row }
nil
end
end
extend Forwardable
delegate [:transaction, :wait_for_notify] => :connection
private
def exec_logged(sql, *args)
Logging.yield_logged sql, *args do
connection.exec_params(sql, Encoder.encode_args(args))
end
end
def enumerate(result, decoder, block)
if block
result.each_row do |row|
block.call decoder.decode(row)
end
self
else
ary = []
result.each_row { |row| ary << decoder.decode(row) }
ary
end
end
def resolve_type(ftype, fmod)
@resolved_types ||= {}
@resolved_types[[ftype, fmod]] ||= connection.exec("SELECT format_type($1,$2)", [ftype, fmod]).getvalue(0, 0)
end
def connection
@connector.call
end
def connector=(connector)
@connector = connector
end
self.connector = lambda {
connect_to_active_record
}
def connect_to_active_record
return Connection.new(ActiveRecord::Base.connection) if defined?(ActiveRecord)
STDERR.puts <<-SQL
Simple::SQL works out of the box with ActiveRecord-based postgres connections, reusing the current connection.
To use without ActiveRecord you must connect to a database via Simple::SQL.connect!.
SQL
raise ArgumentError, "simple-sql: missing connection"
end
public
# connects to the database specified via the url parameter. If called
# without argument it tries to determine a DATABASE_URL from either the
# environment setting (DATABASE_URL) or from a config/database.yml file,
# taking into account the RAILS_ENV and RACK_ENV settings.
def connect!(database_url = :auto)
database_url = Config.determine_url if database_url == :auto
logger.info "Connecting to #{database_url}"
config = Config.parse_url(database_url)
require "pg"
connection = Connection.new(PG::Connection.new(config))
self.connector = lambda { connection }
end
end
end