2016-01-28 23:31:35 +00:00
|
|
|
module DB
|
2016-08-29 19:56:34 +00:00
|
|
|
# Common interface for connection based statements
|
|
|
|
# and for connection pool statements.
|
|
|
|
module StatementMethods
|
|
|
|
# See `QueryMethods#scalar`
|
2019-09-20 20:23:09 +00:00
|
|
|
def scalar(*args_, args : Array? = nil)
|
|
|
|
query(*args_, args: args) do |rs|
|
2016-08-29 19:56:34 +00:00
|
|
|
rs.each do
|
|
|
|
return rs.read
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-17 21:04:23 +00:00
|
|
|
raise NoResultsError.new("no results")
|
2016-08-29 19:56:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# See `QueryMethods#query`
|
2019-09-20 20:23:09 +00:00
|
|
|
def query(*args_, args : Array? = nil)
|
|
|
|
rs = query(*args_, args: args)
|
2016-08-29 19:56:34 +00:00
|
|
|
yield rs ensure rs.close
|
|
|
|
end
|
|
|
|
|
|
|
|
# See `QueryMethods#exec`
|
|
|
|
abstract def exec : ExecResult
|
|
|
|
# See `QueryMethods#exec`
|
2019-09-20 20:23:09 +00:00
|
|
|
abstract def exec(*args_, args : Array? = nil) : ExecResult
|
2016-08-29 19:56:34 +00:00
|
|
|
|
|
|
|
# See `QueryMethods#query`
|
|
|
|
abstract def query : ResultSet
|
|
|
|
# See `QueryMethods#query`
|
2019-09-20 20:23:09 +00:00
|
|
|
abstract def query(*args_, args : Array? = nil) : ResultSet
|
2016-08-29 19:56:34 +00:00
|
|
|
end
|
|
|
|
|
2016-12-04 18:14:43 +00:00
|
|
|
# Represents a query in a `Connection`.
|
2016-01-31 22:40:02 +00:00
|
|
|
# It should be created by `QueryMethods`.
|
|
|
|
#
|
|
|
|
# ### Note to implementors
|
|
|
|
#
|
|
|
|
# 1. Subclass `Statements`
|
|
|
|
# 2. `Statements` are created from a custom driver `Connection#prepare` method.
|
2016-02-02 02:02:04 +00:00
|
|
|
# 3. `#perform_query` executes a query that is expected to return a `ResultSet`
|
|
|
|
# 4. `#perform_exec` executes a query that is expected to return an `ExecResult`
|
|
|
|
# 6. `#do_close` is called to release the statement resources.
|
2016-01-28 23:31:35 +00:00
|
|
|
abstract class Statement
|
2016-08-29 19:56:34 +00:00
|
|
|
include StatementMethods
|
2023-12-08 22:06:41 +00:00
|
|
|
include Disposable
|
|
|
|
|
|
|
|
protected def do_close
|
|
|
|
end
|
2016-02-03 20:10:03 +00:00
|
|
|
|
2016-02-03 21:46:37 +00:00
|
|
|
# :nodoc:
|
2016-01-30 22:46:43 +00:00
|
|
|
getter connection
|
2016-01-29 19:13:01 +00:00
|
|
|
|
2020-09-25 17:49:50 +00:00
|
|
|
getter command : String
|
|
|
|
|
|
|
|
def initialize(@connection : Connection, @command : String)
|
2016-01-28 23:31:35 +00:00
|
|
|
end
|
|
|
|
|
2023-11-29 21:39:44 +00:00
|
|
|
# :nodoc:
|
|
|
|
property auto_close : Bool = false
|
|
|
|
|
|
|
|
# :nodoc:
|
|
|
|
def release_from_result_set
|
|
|
|
self.close if @auto_close
|
|
|
|
self.release_connection
|
|
|
|
end
|
|
|
|
|
2016-02-04 00:28:53 +00:00
|
|
|
def release_connection
|
2016-11-16 02:46:11 +00:00
|
|
|
@connection.release_from_statement
|
2016-02-04 00:28:53 +00:00
|
|
|
end
|
|
|
|
|
2016-02-02 02:02:04 +00:00
|
|
|
# See `QueryMethods#exec`
|
2019-08-02 14:54:52 +00:00
|
|
|
def exec : DB::ExecResult
|
2016-12-07 02:27:15 +00:00
|
|
|
perform_exec_and_release(Slice(Any).empty)
|
2016-02-04 00:28:53 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# See `QueryMethods#exec`
|
2019-09-20 20:23:09 +00:00
|
|
|
def exec(*args_, args : Array? = nil) : DB::ExecResult
|
|
|
|
perform_exec_and_release(EnumerableConcat.build(args_, args))
|
2016-01-29 20:13:05 +00:00
|
|
|
end
|
|
|
|
|
2016-01-31 22:40:02 +00:00
|
|
|
# See `QueryMethods#query`
|
2019-08-02 14:54:52 +00:00
|
|
|
def query : DB::ResultSet
|
2016-09-28 19:45:38 +00:00
|
|
|
perform_query_with_rescue Tuple.new
|
2016-01-30 22:46:43 +00:00
|
|
|
end
|
|
|
|
|
2016-04-14 20:33:39 +00:00
|
|
|
# See `QueryMethods#query`
|
2019-09-20 20:23:09 +00:00
|
|
|
def query(*args_, args : Array? = nil) : DB::ResultSet
|
|
|
|
perform_query_with_rescue(EnumerableConcat.build(args_, args))
|
2016-04-14 20:33:39 +00:00
|
|
|
end
|
|
|
|
|
2016-04-15 04:40:27 +00:00
|
|
|
private def perform_exec_and_release(args : Enumerable) : ExecResult
|
2020-09-25 17:49:50 +00:00
|
|
|
around_query_or_exec(args) do
|
|
|
|
perform_exec(args)
|
|
|
|
end
|
2016-08-29 04:23:20 +00:00
|
|
|
ensure
|
2016-06-29 19:54:16 +00:00
|
|
|
release_connection
|
2016-02-04 00:28:53 +00:00
|
|
|
end
|
|
|
|
|
2016-09-28 19:45:38 +00:00
|
|
|
private def perform_query_with_rescue(args : Enumerable) : ResultSet
|
2020-09-25 17:49:50 +00:00
|
|
|
around_query_or_exec(args) do
|
|
|
|
perform_query(args)
|
|
|
|
end
|
2016-09-28 19:45:38 +00:00
|
|
|
rescue e : Exception
|
|
|
|
# Release connection only when an exception occurs during the query
|
|
|
|
# execution since we need the connection open while the ResultSet is open
|
|
|
|
release_connection
|
|
|
|
raise e
|
|
|
|
end
|
|
|
|
|
2016-04-14 20:33:39 +00:00
|
|
|
protected abstract def perform_query(args : Enumerable) : ResultSet
|
2016-04-15 04:40:27 +00:00
|
|
|
protected abstract def perform_exec(args : Enumerable) : ExecResult
|
2020-09-25 17:49:50 +00:00
|
|
|
|
|
|
|
# This method is called when executing the statement. Although it can be
|
|
|
|
# redefined, it is recommended to use the `def_around_query_or_exec` macro
|
|
|
|
# to be able to add new behaviors without loosing prior existing ones.
|
|
|
|
protected def around_query_or_exec(args : Enumerable)
|
|
|
|
yield
|
|
|
|
end
|
|
|
|
|
|
|
|
# This macro allows injecting code to be run before and after the execution
|
|
|
|
# of the request. It should return the yielded value. It must be called with 1
|
|
|
|
# block argument that will be used to pass the `args : Enumerable`.
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# class DB::Statement
|
|
|
|
# def_around_query_or_exec do |args|
|
|
|
|
# # do something before query or exec
|
|
|
|
# res = yield
|
|
|
|
# # do something after query or exec
|
|
|
|
# res
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# ```
|
|
|
|
macro def_around_query_or_exec(&block)
|
|
|
|
protected def around_query_or_exec(%args : Enumerable)
|
|
|
|
previous_def do
|
|
|
|
{% if block.args.size != 1 %}
|
|
|
|
{% raise "Wrong number of block arguments (given #{block.args.size}, expected: 1)" %}
|
|
|
|
{% end %}
|
|
|
|
|
|
|
|
{{ block.args.first.id }} = %args
|
|
|
|
{{ block.body }}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def_around_query_or_exec do |args|
|
|
|
|
emit_log(args)
|
|
|
|
yield
|
|
|
|
end
|
|
|
|
|
|
|
|
protected def emit_log(args : Enumerable)
|
|
|
|
Log.debug &.emit("Executing query", query: command, args: MetadataValueConverter.arg_to_log(args))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# This module converts DB supported values to `::Log::Metadata::Value`
|
|
|
|
#
|
|
|
|
# ### Note to implementors
|
|
|
|
#
|
|
|
|
# If the driver defines custom types to be used as arguments the default behavior
|
|
|
|
# will be converting the value via `#to_s`. Otherwise you can define overloads to
|
|
|
|
# change this behaviour.
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# module DB::MetadataValueConverter
|
|
|
|
# def self.arg_to_log(arg : PG::Geo::Point)
|
|
|
|
# ::Log::Metadata::Value.new("(#{arg.x}, #{arg.y})::point")
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# ```
|
|
|
|
module MetadataValueConverter
|
|
|
|
# Returns *arg* encoded as a `::Log::Metadata::Value`.
|
|
|
|
def self.arg_to_log(arg) : ::Log::Metadata::Value
|
|
|
|
::Log::Metadata::Value.new(arg.to_s)
|
|
|
|
end
|
|
|
|
|
|
|
|
# :ditto:
|
|
|
|
def self.arg_to_log(arg : Enumerable) : ::Log::Metadata::Value
|
|
|
|
::Log::Metadata::Value.new(arg.to_a.map { |a| arg_to_log(a).as(::Log::Metadata::Value) })
|
|
|
|
end
|
|
|
|
|
|
|
|
# :ditto:
|
|
|
|
def self.arg_to_log(arg : Int) : ::Log::Metadata::Value
|
|
|
|
::Log::Metadata::Value.new(arg.to_i64)
|
|
|
|
end
|
|
|
|
|
|
|
|
# :ditto:
|
|
|
|
def self.arg_to_log(arg : UInt64) : ::Log::Metadata::Value
|
|
|
|
::Log::Metadata::Value.new(arg.to_s)
|
|
|
|
end
|
|
|
|
|
|
|
|
# :ditto:
|
|
|
|
def self.arg_to_log(arg : Nil | Bool | Int32 | Int64 | Float32 | Float64 | String | Time) : ::Log::Metadata::Value
|
|
|
|
::Log::Metadata::Value.new(arg)
|
|
|
|
end
|
2016-01-28 23:31:35 +00:00
|
|
|
end
|
|
|
|
end
|