2016-01-28 23:31:35 +00:00
|
|
|
module DB
|
2016-01-31 22:40:02 +00:00
|
|
|
# The response of a query performed on a `Database`.
|
|
|
|
#
|
|
|
|
# See `DB` for a complete sample.
|
|
|
|
#
|
|
|
|
# Each `#read` call consumes the result and moves to the next column.
|
2016-02-19 21:42:30 +00:00
|
|
|
# Each column must be read in order.
|
|
|
|
# At any moment a `#move_next` can be invoked, meaning to skip the
|
|
|
|
# remaining, or even all the columns, in the current row.
|
|
|
|
# Also it is not mandatory to consume the whole `ResultSet`, hence an iteration
|
|
|
|
# through `#each` or `#move_next` can be stopped.
|
|
|
|
#
|
|
|
|
# **Note:** depending on how the `ResultSet` was obtained it might be mandatory an
|
|
|
|
# explicit call to `#close`. Check `QueryMethods#query`.
|
2016-01-31 22:40:02 +00:00
|
|
|
#
|
|
|
|
# ### Note to implementors
|
|
|
|
#
|
|
|
|
# 1. Override `#move_next` to move to the next row.
|
2016-06-28 17:02:08 +00:00
|
|
|
# 2. Override `#read` returning the next value in the row.
|
|
|
|
# 3. (Optional) Override `#read(t)` for some types `t` for which custom logic other than a simple cast is needed.
|
2016-01-31 22:40:02 +00:00
|
|
|
# 4. Override `#column_count`, `#column_name`.
|
2016-01-28 23:31:35 +00:00
|
|
|
abstract class ResultSet
|
2016-02-03 20:10:03 +00:00
|
|
|
include Disposable
|
|
|
|
|
2016-02-03 21:46:37 +00:00
|
|
|
# :nodoc:
|
2016-01-28 23:31:35 +00:00
|
|
|
getter statement
|
|
|
|
|
2016-02-04 00:51:32 +00:00
|
|
|
def initialize(@statement : DB::Statement)
|
2016-01-28 23:31:35 +00:00
|
|
|
end
|
|
|
|
|
2016-02-03 22:30:51 +00:00
|
|
|
protected def do_close
|
2023-11-29 21:39:44 +00:00
|
|
|
statement.release_from_result_set
|
2016-02-03 22:30:51 +00:00
|
|
|
end
|
|
|
|
|
2016-02-02 00:55:30 +00:00
|
|
|
# TODO add_next_result_set : Bool
|
|
|
|
|
2016-01-31 22:40:02 +00:00
|
|
|
# Iterates over all the rows
|
2016-01-28 23:51:03 +00:00
|
|
|
def each
|
2016-01-29 19:13:01 +00:00
|
|
|
while move_next
|
2016-01-28 23:51:03 +00:00
|
|
|
yield
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-28 22:29:15 +00:00
|
|
|
# Iterates over all the columns
|
|
|
|
def each_column
|
|
|
|
column_count.times do |x|
|
2016-07-04 15:13:39 +00:00
|
|
|
yield column_name(x)
|
2016-03-28 22:29:15 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-01-31 22:40:02 +00:00
|
|
|
# Move the next row in the result.
|
|
|
|
# Return `false` if no more rows are available.
|
|
|
|
# See `#each`
|
2016-01-29 19:13:01 +00:00
|
|
|
abstract def move_next : Bool
|
2016-01-28 23:31:35 +00:00
|
|
|
|
2016-01-30 22:46:43 +00:00
|
|
|
# TODO def empty? : Bool, handle internally with move_next (?)
|
|
|
|
|
2016-01-31 22:40:02 +00:00
|
|
|
# Returns the number of columns in the result
|
2016-01-30 00:57:00 +00:00
|
|
|
abstract def column_count : Int32
|
2016-01-31 22:40:02 +00:00
|
|
|
|
|
|
|
# Returns the name of the column in `index` 0-based position.
|
2016-01-30 00:57:00 +00:00
|
|
|
abstract def column_name(index : Int32) : String
|
2016-01-31 22:40:02 +00:00
|
|
|
|
2016-07-18 00:50:52 +00:00
|
|
|
# Returns the name of the columns.
|
|
|
|
def column_names
|
|
|
|
Array(String).new(column_count) { |i| column_name(i) }
|
|
|
|
end
|
|
|
|
|
2016-06-28 17:02:08 +00:00
|
|
|
# Reads the next column value
|
|
|
|
abstract def read
|
2016-01-30 00:57:00 +00:00
|
|
|
|
2021-10-12 23:49:26 +00:00
|
|
|
# Returns the column index that corresponds to the next `#read`.
|
|
|
|
#
|
|
|
|
# If the last column of the current row has been read, it must return `#column_count`.
|
|
|
|
abstract def next_column_index : Int32
|
|
|
|
|
2016-07-04 15:46:45 +00:00
|
|
|
# Reads the next columns and maps them to a class
|
|
|
|
def read(type : DB::Mappable.class)
|
|
|
|
type.new(self)
|
|
|
|
end
|
|
|
|
|
2016-06-28 17:02:08 +00:00
|
|
|
# Reads the next column value as a **type**
|
2016-09-19 22:20:18 +00:00
|
|
|
def read(type : T.class) : T forall T
|
2021-10-12 23:49:26 +00:00
|
|
|
col_index = next_column_index
|
2017-04-06 18:28:20 +00:00
|
|
|
value = read
|
|
|
|
if value.is_a?(T)
|
|
|
|
value
|
|
|
|
else
|
2021-10-12 23:49:26 +00:00
|
|
|
raise DB::ColumnTypeMismatchError.new(
|
|
|
|
context: "#{self.class}#read",
|
|
|
|
column_index: col_index,
|
|
|
|
column_name: column_name(col_index),
|
|
|
|
column_type: value.class.to_s,
|
|
|
|
expected_type: T.to_s
|
|
|
|
)
|
2017-04-06 18:28:20 +00:00
|
|
|
end
|
2016-06-21 15:10:09 +00:00
|
|
|
end
|
2016-02-02 00:55:30 +00:00
|
|
|
|
2022-10-26 14:06:14 +00:00
|
|
|
# Read the value based on the given `enum` type, supporting both string and
|
|
|
|
# numeric column types.
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# enum Status
|
|
|
|
# Pending
|
|
|
|
# Complete
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# db.query "SELECT 'complete'" do |rs|
|
|
|
|
# rs.read Status # => Status::Complete
|
|
|
|
# end
|
|
|
|
# ```
|
|
|
|
def read(type : Enum.class)
|
|
|
|
type.new(self)
|
|
|
|
end
|
|
|
|
|
2016-06-28 17:02:08 +00:00
|
|
|
# Reads the next columns and returns a tuple of the values.
|
|
|
|
def read(*types : Class)
|
|
|
|
internal_read(*types)
|
2016-04-10 18:21:10 +00:00
|
|
|
end
|
|
|
|
|
2017-12-29 22:32:25 +00:00
|
|
|
# Reads the next columns and returns a named tuple of the values.
|
|
|
|
def read(**types : Class)
|
|
|
|
internal_read(**types)
|
|
|
|
end
|
|
|
|
|
2016-09-19 22:20:18 +00:00
|
|
|
private def internal_read(*types : *T) forall T
|
2016-06-28 17:02:08 +00:00
|
|
|
{% begin %}
|
|
|
|
Tuple.new(
|
|
|
|
{% for type in T %}
|
|
|
|
read({{type.instance}}),
|
|
|
|
{% end %}
|
|
|
|
)
|
|
|
|
{% end %}
|
2016-04-14 18:18:09 +00:00
|
|
|
end
|
|
|
|
|
2017-12-29 22:32:25 +00:00
|
|
|
private def internal_read(**types : **T) forall T
|
|
|
|
{% begin %}
|
|
|
|
NamedTuple.new(
|
|
|
|
{% for name, type in T %}
|
|
|
|
{{ name }}: read({{type.instance}}),
|
|
|
|
{% end %}
|
|
|
|
)
|
|
|
|
{% end %}
|
|
|
|
end
|
|
|
|
|
2016-02-02 00:55:30 +00:00
|
|
|
# def read_blob
|
|
|
|
# yield ... io ....
|
|
|
|
# end
|
|
|
|
|
|
|
|
# def read_text
|
|
|
|
# yield ... io ....
|
|
|
|
# end
|
2016-01-28 23:31:35 +00:00
|
|
|
end
|
|
|
|
end
|
2022-10-26 14:06:14 +00:00
|
|
|
|
|
|
|
struct Enum
|
|
|
|
def self.new(rs : DB::ResultSet) : self
|
|
|
|
index = rs.next_column_index
|
|
|
|
|
|
|
|
case value = rs.read
|
|
|
|
when String
|
|
|
|
parse value
|
|
|
|
when Int
|
|
|
|
from_value value
|
|
|
|
else
|
|
|
|
raise DB::ColumnTypeMismatchError.new(
|
|
|
|
context: "#{self}.new(rs : DB::ResultSet)",
|
|
|
|
column_index: index,
|
|
|
|
column_name: rs.column_name(index),
|
|
|
|
column_type: value.class.to_s,
|
|
|
|
expected_type: "String | Int",
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|