mirror of
https://gitea.invidious.io/iv-org/shard-crystal-db.git
synced 2024-08-15 00:53:32 +00:00
switch to 0-pased positional arguments
add docs, many docs
This commit is contained in:
parent
a96776e336
commit
fd804dd592
9 changed files with 229 additions and 30 deletions
|
@ -86,7 +86,7 @@ class DummyDriver < DB::Driver
|
||||||
return nil if n == "NULL"
|
return nil if n == "NULL"
|
||||||
|
|
||||||
if n == "?"
|
if n == "?"
|
||||||
return @statement.params[1]
|
return @statement.params[0]
|
||||||
end
|
end
|
||||||
|
|
||||||
if n.starts_with?(":")
|
if n.starts_with?(":")
|
||||||
|
|
|
@ -13,9 +13,9 @@ describe DB::Statement do
|
||||||
with_dummy do |db|
|
with_dummy do |db|
|
||||||
stmt = db.prepare("the query")
|
stmt = db.prepare("the query")
|
||||||
stmt.query "a", 1, nil
|
stmt.query "a", 1, nil
|
||||||
stmt.params[1].should eq("a")
|
stmt.params[0].should eq("a")
|
||||||
stmt.params[2].should eq(1)
|
stmt.params[1].should eq(1)
|
||||||
stmt.params[3].should eq(nil)
|
stmt.params[2].should eq(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -43,9 +43,9 @@ describe DB::Statement do
|
||||||
with_dummy do |db|
|
with_dummy do |db|
|
||||||
stmt = db.prepare("the query")
|
stmt = db.prepare("the query")
|
||||||
stmt.exec "a", 1, nil
|
stmt.exec "a", 1, nil
|
||||||
stmt.params[1].should eq("a")
|
stmt.params[0].should eq("a")
|
||||||
stmt.params[2].should eq(1)
|
stmt.params[1].should eq(1)
|
||||||
stmt.params[3].should eq(nil)
|
stmt.params[2].should eq(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -73,9 +73,9 @@ describe DB::Statement do
|
||||||
with_dummy do |db|
|
with_dummy do |db|
|
||||||
stmt = db.prepare("the query")
|
stmt = db.prepare("the query")
|
||||||
stmt.scalar String, "a", 1, nil
|
stmt.scalar String, "a", 1, nil
|
||||||
stmt.params[1].should eq("a")
|
stmt.params[0].should eq("a")
|
||||||
stmt.params[2].should eq(1)
|
stmt.params[1].should eq(1)
|
||||||
stmt.params[3].should eq(nil)
|
stmt.params[2].should eq(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -103,9 +103,9 @@ describe DB::Statement do
|
||||||
with_dummy do |db|
|
with_dummy do |db|
|
||||||
stmt = db.prepare("the query")
|
stmt = db.prepare("the query")
|
||||||
stmt.scalar? String, "a", 1, nil
|
stmt.scalar? String, "a", 1, nil
|
||||||
stmt.params[1].should eq("a")
|
stmt.params[0].should eq("a")
|
||||||
stmt.params[2].should eq(1)
|
stmt.params[1].should eq(1)
|
||||||
stmt.params[3].should eq(nil)
|
stmt.params[2].should eq(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,19 @@
|
||||||
module DB
|
module DB
|
||||||
|
# Database driver implementors must subclass `Connection`.
|
||||||
|
#
|
||||||
|
# Represents one active connection to a database.
|
||||||
|
#
|
||||||
|
# Users should never instantiate a `Connection` manually. Use `DB#open` or `Database#connection`.
|
||||||
|
#
|
||||||
|
# Refer to `QueryMethods` for documentation about querying the database through this connection.
|
||||||
|
#
|
||||||
|
# ### Note to implementors
|
||||||
|
#
|
||||||
|
# The connection must be initialized in `#initialize` and closed in `#perform_close`.
|
||||||
|
#
|
||||||
|
# To allow quering override `#prepare` method in order to return a prepared `Statement`.
|
||||||
|
# Also override `#last_insert_id` to allow safe access to the last inserted id through this connection.
|
||||||
|
#
|
||||||
abstract class Connection
|
abstract class Connection
|
||||||
getter connection_string
|
getter connection_string
|
||||||
|
|
||||||
|
@ -23,10 +38,12 @@ module DB
|
||||||
# close unless closed?
|
# close unless closed?
|
||||||
# end
|
# end
|
||||||
|
|
||||||
|
# Returns an `Statement` with the prepared `query`
|
||||||
abstract def prepare(query) : Statement
|
abstract def prepare(query) : Statement
|
||||||
|
|
||||||
include QueryMethods
|
include QueryMethods
|
||||||
|
|
||||||
|
# Returns the last inserted id through this connection.
|
||||||
abstract def last_insert_id : Int64
|
abstract def last_insert_id : Int64
|
||||||
|
|
||||||
protected abstract def perform_close
|
protected abstract def perform_close
|
||||||
|
|
|
@ -3,22 +3,29 @@ module DB
|
||||||
# Currently it creates a single connection to the database.
|
# Currently it creates a single connection to the database.
|
||||||
# Eventually a connection pool will be handled.
|
# Eventually a connection pool will be handled.
|
||||||
#
|
#
|
||||||
# It should be created from DB module. See `DB.open`.
|
# It should be created from DB module. See `DB#open`.
|
||||||
|
#
|
||||||
|
# Refer to `QueryMethods` for documentation about querying the database.
|
||||||
class Database
|
class Database
|
||||||
|
# :nodoc:
|
||||||
getter driver_class
|
getter driver_class
|
||||||
|
|
||||||
|
# Connection configuration to the database.
|
||||||
getter connection_string
|
getter connection_string
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
def initialize(@driver_class, @connection_string)
|
def initialize(@driver_class, @connection_string)
|
||||||
@driver = @driver_class.new(@connection_string)
|
@driver = @driver_class.new(@connection_string)
|
||||||
@connection = @driver.build_connection
|
@connection = @driver.build_connection
|
||||||
end
|
end
|
||||||
|
|
||||||
# Closes all connection to the database
|
# Closes all connection to the database.
|
||||||
def close
|
def close
|
||||||
@connection.close
|
@connection.close
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a `Connection` to the database
|
# Returns a `Connection` to the database.
|
||||||
|
# Useful if you need to ensure the statements are executed in the connection.
|
||||||
def connection
|
def connection
|
||||||
@connection
|
@connection
|
||||||
end
|
end
|
||||||
|
|
87
src/db/db.cr
87
src/db/db.cr
|
@ -1,23 +1,94 @@
|
||||||
|
# The DB module is a unified interface to database access.
|
||||||
|
# Database dialects is supported by custom database driver shards.
|
||||||
|
# Check [manastech/crystal-sqlite3](https://github.com/manastech/crystal-sqlite3) for example.
|
||||||
|
#
|
||||||
|
# Drivers implementors check `Driver` class.
|
||||||
|
#
|
||||||
|
# Currently a *single connection* to the database is stablished.
|
||||||
|
# In the future a connection pool and transaction support will be available.
|
||||||
|
#
|
||||||
|
# ### Usage
|
||||||
|
#
|
||||||
|
# Assuming `crystal-sqlite3` is included a sqlite3 database can be opened with `#open`.
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# db = DB.open "sqlite3", ":memory:" # or the sqlite3 file path
|
||||||
|
# db.close
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# If a block is given to `#open` the database is closed automatically
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# DB.open "sqlite3", ":memory:" do |db|
|
||||||
|
# # work with db
|
||||||
|
# end # db is closed
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# Three kind of statements can be performed:
|
||||||
|
# 1. `Database#exec` waits no response from the database.
|
||||||
|
# 2. `Database#scalar` reads a single value of the response.
|
||||||
|
# 3. `Database#query` returns a ResultSet that allows iteration over the rows in the response and column information.
|
||||||
|
#
|
||||||
|
# All of the above methods allows parametrised query. Either positional or named arguments.
|
||||||
|
#
|
||||||
|
# Check a full working version:
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# require "db"
|
||||||
|
# require "sqlite3"
|
||||||
|
#
|
||||||
|
# DB.open "sqlite3", ":memory:" do |db|
|
||||||
|
# db.exec "create table contacts (name string, age integer)"
|
||||||
|
# db.exec "insert into contacts values (?, ?)", "John Doe", 30
|
||||||
|
# db.exec "insert into contacts values (:name, :age)", {name: "Sarah", age: 33}
|
||||||
|
#
|
||||||
|
# puts "max age:"
|
||||||
|
# puts db.scalar "select max(age) from contacts" # => 33
|
||||||
|
#
|
||||||
|
# puts "contacts:"
|
||||||
|
# db.query "select name, age from contacts order by age desc" do |rs|
|
||||||
|
# puts "#{rs.column_name(0)} (#{rs.column_name(1)})"
|
||||||
|
# # => name (age)
|
||||||
|
# rs.each do
|
||||||
|
# puts "#{rs.read(String)} (#{rs.read(Int32)})"
|
||||||
|
# # => Sarah (33)
|
||||||
|
# # => John Doe (30)
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# ```
|
||||||
|
#
|
||||||
module DB
|
module DB
|
||||||
|
# Types supported to interface with database driver.
|
||||||
|
# These can be used in any `ResultSet#read` or any `Database#query` related
|
||||||
|
# method to be used as query parameters
|
||||||
TYPES = [String, Int32, Int64, Float32, Float64, Slice(UInt8)]
|
TYPES = [String, Int32, Int64, Float32, Float64, Slice(UInt8)]
|
||||||
|
|
||||||
|
# See `DB::TYPES` in `DB`
|
||||||
alias Any = String | Int32 | Int64 | Float32 | Float64 | Slice(UInt8)
|
alias Any = String | Int32 | Int64 | Float32 | Float64 | Slice(UInt8)
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
def self.driver_class(name) # : Driver.class
|
def self.driver_class(driver_name) # : Driver.class
|
||||||
@@drivers.not_nil![name]
|
@@drivers.not_nil![driver_name]
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.register_driver(name, klass : Driver.class)
|
# Registers a driver class for a given `driver_name`.
|
||||||
|
# Should be called by drivers implementors only.
|
||||||
|
def self.register_driver(driver_name, driver_class : Driver.class)
|
||||||
@@drivers ||= {} of String => Driver.class
|
@@drivers ||= {} of String => Driver.class
|
||||||
@@drivers.not_nil![name] = klass
|
@@drivers.not_nil![driver_name] = driver_class
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.open(name, connection_string)
|
# Opens a database using the `driver_name` registered driver.
|
||||||
Database.new(driver_class(name), connection_string)
|
# Uses `connection_string` for connection configuration.
|
||||||
|
# Returned database must be closed by `Database#close`.
|
||||||
|
def self.open(driver_name, connection_string)
|
||||||
|
Database.new(driver_class(driver_name), connection_string)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.open(name, connection_string, &block)
|
# Same as `#open` but the database is yielded and closed automatically.
|
||||||
open(name, connection_string).tap do |db|
|
def self.open(driver_name, connection_string, &block)
|
||||||
|
open(driver_name, connection_string).tap do |db|
|
||||||
yield db
|
yield db
|
||||||
db.close
|
db.close
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,30 @@
|
||||||
module DB
|
module DB
|
||||||
|
# Database driver implementors must subclass `Driver`,
|
||||||
|
# register with a driver_name using `DB#register_driver` and
|
||||||
|
# override the factory method `#build_connection`.
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# require "db"
|
||||||
|
#
|
||||||
|
# class FakeDriver < Driver
|
||||||
|
# def build_connection
|
||||||
|
# FakeConnection.new connection_string
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# DB.register_driver "fake", FakeDriver
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# Access to this fake datbase will be available with
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# DB.open "fake", "..." do |db|
|
||||||
|
# # ... use db ...
|
||||||
|
# end
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# Refer to `Connection`, `Statement` and `ResultSet` for further
|
||||||
|
# driver implementation instructions.
|
||||||
abstract class Driver
|
abstract class Driver
|
||||||
getter connection_string
|
getter connection_string
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,29 @@
|
||||||
module DB
|
module DB
|
||||||
|
# Methods to allow querying a database.
|
||||||
|
# All methods accepts a `query : String` and a set arguments.
|
||||||
|
#
|
||||||
|
# Three kind of statements can be performed:
|
||||||
|
# 1. `#exec` waits no response from the database.
|
||||||
|
# 2. `#scalar` reads a single value of the response. Use `#scalar?` if the response is nillable.
|
||||||
|
# 3. `#query` returns a ResultSet that allows iteration over the rows in the response and column information.
|
||||||
|
#
|
||||||
|
# Arguments can be passed:
|
||||||
|
# * by position: `db.query("SELECT name FROM ... WHERE age > ?", age)`
|
||||||
|
# * by symbol: `db.query("SELECT name FROM ... WHERE age > :age", {age: age})`
|
||||||
|
# * by string: `db.query("SELECT name FROM ... WHERE age > :age", {"age": age})`
|
||||||
|
#
|
||||||
|
# Convention of mapping how arguments are mapped to the query depends on each driver.
|
||||||
|
#
|
||||||
|
# Including `QueryMethods` requires a `prepare(query) : Statement` method.
|
||||||
module QueryMethods
|
module QueryMethods
|
||||||
|
# Returns a `ResultSet` for the `query`.
|
||||||
|
# The `ResultSet` must be closed manually.
|
||||||
def query(query, *args)
|
def query(query, *args)
|
||||||
prepare(query).query(*args)
|
prepare(query).query(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Yields a `ResultSet` for the `query`.
|
||||||
|
# The `ResultSet` is closed automatically.
|
||||||
def query(query, *args)
|
def query(query, *args)
|
||||||
# CHECK prepare(query).query(*args, &block)
|
# CHECK prepare(query).query(*args, &block)
|
||||||
query(query, *args).tap do |rs|
|
query(query, *args).tap do |rs|
|
||||||
|
@ -15,22 +35,37 @@ module DB
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Performs the `query` discarding any response
|
||||||
def exec(query, *args)
|
def exec(query, *args)
|
||||||
prepare(query).exec(*args)
|
prepare(query).exec(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Performs the `query` and returns a single scalar `Int32` value
|
||||||
def scalar(query, *args)
|
def scalar(query, *args)
|
||||||
prepare(query).scalar(*args)
|
prepare(query).scalar(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Performs the `query` and returns a single scalar value of type `t`.
|
||||||
|
# `t` must be any of the allowed `DB::Any` types.
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# puts db.scalar(String, "SELECT MAX(name)") # => (a String)
|
||||||
|
# ```
|
||||||
def scalar(t, query, *args)
|
def scalar(t, query, *args)
|
||||||
prepare(query).scalar(t, *args)
|
prepare(query).scalar(t, *args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Performs the `query` and returns a single scalar `Int32 | Nil` value
|
||||||
def scalar?(query, *args)
|
def scalar?(query, *args)
|
||||||
prepare(query).scalar?(*args)
|
prepare(query).scalar?(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Performs the `query` and returns a single scalar value of type `t` or `Nil`.
|
||||||
|
# `t` must be any of the allowed `DB::Any` types.
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# puts db.scalar?(String, "SELECT MAX(name)") # => (a String | Nil)
|
||||||
|
# ```
|
||||||
def scalar?(t, query, *args)
|
def scalar?(t, query, *args)
|
||||||
prepare(query).scalar?(t, *args)
|
prepare(query).scalar?(t, *args)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,39 +1,66 @@
|
||||||
module DB
|
module DB
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# ### Note to implementors
|
||||||
|
#
|
||||||
|
# 1. Override `#move_next` to move to the next row.
|
||||||
|
# 2. Override `#read?(t)` for all `t` in `DB::TYPES`.
|
||||||
|
# 3. (Optional) Override `#read(t)` for all `t` in `DB::TYPES`.
|
||||||
|
# 4. Override `#column_count`, `#column_name`.
|
||||||
|
# 5. Override `#column_type`. It must return a type in `DB::TYPES`.
|
||||||
abstract class ResultSet
|
abstract class ResultSet
|
||||||
|
# :nodoc:
|
||||||
getter statement
|
getter statement
|
||||||
|
|
||||||
def initialize(@statement : Statement)
|
def initialize(@statement : Statement)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Iterates over all the rows
|
||||||
def each
|
def each
|
||||||
while move_next
|
while move_next
|
||||||
yield
|
yield
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Closes the result set.
|
||||||
def close
|
def close
|
||||||
@statement.close
|
@statement.close
|
||||||
end
|
end
|
||||||
|
|
||||||
# :nodoc:
|
|
||||||
# Ensures it executes the query
|
# Ensures it executes the query
|
||||||
def exec
|
def exec
|
||||||
move_next
|
move_next
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Move the next row in the result.
|
||||||
|
# Return `false` if no more rows are available.
|
||||||
|
# See `#each`
|
||||||
abstract def move_next : Bool
|
abstract def move_next : Bool
|
||||||
|
|
||||||
# TODO def empty? : Bool, handle internally with move_next (?)
|
# TODO def empty? : Bool, handle internally with move_next (?)
|
||||||
|
|
||||||
|
# Returns the number of columns in the result
|
||||||
abstract def column_count : Int32
|
abstract def column_count : Int32
|
||||||
|
|
||||||
|
# Returns the name of the column in `index` 0-based position.
|
||||||
abstract def column_name(index : Int32) : String
|
abstract def column_name(index : Int32) : String
|
||||||
|
|
||||||
|
# Returns the type of the column in `index` 0-based position.
|
||||||
|
# The result is one of `DB::TYPES`.
|
||||||
abstract def column_type(index : Int32)
|
abstract def column_type(index : Int32)
|
||||||
|
|
||||||
# list datatypes that must be supported form the driver
|
# list datatypes that must be supported form the driver
|
||||||
# users will call read(String) or read?(String) for nillables
|
# users will call read(String) or read?(String) for nillables
|
||||||
|
|
||||||
{% for t in DB::TYPES %}
|
{% for t in DB::TYPES %}
|
||||||
|
# Reads the next column as a nillable {{t}}.
|
||||||
abstract def read?(t : {{t}}.class) : {{t}}?
|
abstract def read?(t : {{t}}.class) : {{t}}?
|
||||||
|
|
||||||
|
# Reads the next column as a {{t}}.
|
||||||
def read(t : {{t}}.class) : {{t}}
|
def read(t : {{t}}.class) : {{t}}
|
||||||
read?({{t}}).not_nil!
|
read?({{t}}).not_nil!
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,15 @@
|
||||||
module DB
|
module DB
|
||||||
|
# Represents a prepared query in a `Connection`.
|
||||||
|
# It should be created by `QueryMethods`.
|
||||||
|
#
|
||||||
|
# ### Note to implementors
|
||||||
|
#
|
||||||
|
# 1. Subclass `Statements`
|
||||||
|
# 2. `Statements` are created from a custom driver `Connection#prepare` method.
|
||||||
|
# 3. `#begin_parameters` is called before the parameters are set.
|
||||||
|
# 4. `#add_parameter` methods helps to support 0-based positional arguments and named arguments
|
||||||
|
# 5. After parameters are set `#perform` is called to return a `ResultSet`
|
||||||
|
# 6. `#on_close` is called to release the statement resources.
|
||||||
abstract class Statement
|
abstract class Statement
|
||||||
getter connection
|
getter connection
|
||||||
|
|
||||||
|
@ -6,17 +17,19 @@ module DB
|
||||||
@closed = false
|
@closed = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#exec`
|
||||||
def exec(*args)
|
def exec(*args)
|
||||||
query(*args) do |rs|
|
query(*args) do |rs|
|
||||||
rs.exec
|
rs.exec
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#scalar`
|
||||||
def scalar(*args)
|
def scalar(*args)
|
||||||
scalar(Int32, *args)
|
scalar(Int32, *args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# t in DB::TYPES
|
# See `QueryMethods#scalar`. `t` must be in DB::TYPES
|
||||||
def scalar(t, *args)
|
def scalar(t, *args)
|
||||||
query(*args) do |rs|
|
query(*args) do |rs|
|
||||||
rs.each do
|
rs.each do
|
||||||
|
@ -27,11 +40,12 @@ module DB
|
||||||
raise "no results"
|
raise "no results"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#scalar?`
|
||||||
def scalar?(*args)
|
def scalar?(*args)
|
||||||
scalar?(Int32, *args)
|
scalar?(Int32, *args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# t in DB::TYPES
|
# See `QueryMethods#scalar?`. `t` must be in DB::TYPES
|
||||||
def scalar?(t, *args)
|
def scalar?(t, *args)
|
||||||
query(*args) do |rs|
|
query(*args) do |rs|
|
||||||
rs.each do
|
rs.each do
|
||||||
|
@ -42,10 +56,12 @@ module DB
|
||||||
raise "no results"
|
raise "no results"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#query`
|
||||||
def query(*args)
|
def query(*args)
|
||||||
execute *args
|
execute *args
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#query`
|
||||||
def query(*args)
|
def query(*args)
|
||||||
execute(*args).tap do |rs|
|
execute(*args).tap do |rs|
|
||||||
begin
|
begin
|
||||||
|
@ -62,13 +78,13 @@ module DB
|
||||||
|
|
||||||
private def execute(arg : Slice(UInt8))
|
private def execute(arg : Slice(UInt8))
|
||||||
begin_parameters
|
begin_parameters
|
||||||
add_parameter 1, arg
|
add_parameter 0, arg
|
||||||
perform
|
perform
|
||||||
end
|
end
|
||||||
|
|
||||||
private def execute(args : Enumerable)
|
private def execute(args : Enumerable)
|
||||||
begin_parameters
|
begin_parameters
|
||||||
args.each_with_index(1) do |arg, index|
|
args.each_with_index do |arg, index|
|
||||||
if arg.is_a?(Hash)
|
if arg.is_a?(Hash)
|
||||||
arg.each do |key, value|
|
arg.each do |key, value|
|
||||||
add_parameter key.to_s, value
|
add_parameter key.to_s, value
|
||||||
|
@ -97,7 +113,7 @@ module DB
|
||||||
# close unless closed?
|
# close unless closed?
|
||||||
# end
|
# end
|
||||||
|
|
||||||
# 1-based positional arguments
|
# 0-based positional arguments
|
||||||
protected def begin_parameters
|
protected def begin_parameters
|
||||||
end
|
end
|
||||||
protected abstract def add_parameter(index : Int32, value)
|
protected abstract def add_parameter(index : Int32, value)
|
||||||
|
|
Loading…
Reference in a new issue