2016-02-03 21:29:09 +00:00
require " uri "
2020-09-25 17:49:50 +00:00
require " log "
2016-02-03 21:29:09 +00:00
2019-07-02 13:01:17 +00:00
# The DB module is a unified interface for database access.
# Individual database systems are supported by specific database driver shards.
2016-01-31 22:40:02 +00:00
#
2019-07-02 13:01:17 +00:00
# Available drivers include:
# * [crystal-lang/crystal-sqlite3](https://github.com/crystal-lang/crystal-sqlite3) for SQLite
# * [crystal-lang/crystal-mysql](https://github.com/crystal-lang/crystal-mysql) for MySQL and MariaDB
# * [will/crystal-pg](https://github.com/will/crystal-pg) for PostgreSQL
# * [kaukas/crystal-cassandra](https://github.com/kaukas/crystal-cassandra) for Cassandra
2016-01-31 22:40:02 +00:00
#
2019-07-02 13:01:17 +00:00
# For basic instructions on implementing a new database driver, check `Driver` and the existing drivers.
#
# DB manages a connection pool. The connection pool can be configured by query parameters to the
# connection `URI` as described in `Database`.
2016-01-31 22:40:02 +00:00
#
# ### Usage
#
2019-07-02 13:01:17 +00:00
# Assuming `crystal-sqlite3` is included a SQLite3 database can be opened with `#open`.
2016-01-31 22:40:02 +00:00
#
# ```
2016-02-16 00:56:45 +00:00
# db = DB.open "sqlite3:./path/to/db/file.db"
2016-01-31 22:40:02 +00:00
# db.close
# ```
#
# If a block is given to `#open` the database is closed automatically
#
# ```
2016-02-16 00:56:45 +00:00
# DB.open "sqlite3:./file.db" do |db|
2016-01-31 22:40:02 +00:00
# # work with db
# end # db is closed
# ```
#
2016-02-16 00:56:45 +00:00
# In the code above `db` is a `Database`. Methods available for querying it are described in `QueryMethods`.
#
2016-01-31 22:40:02 +00:00
# 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:
#
2019-07-02 13:01:17 +00:00
# The following example uses SQLite where `?` indicates the arguments. If PostgreSQL is used `$1`, `$2`, etc. should be used. `crystal-db` does not interpret the statements.
#
2016-01-31 22:40:02 +00:00
# ```
# require "db"
# require "sqlite3"
#
2016-02-16 00:56:45 +00:00
# DB.open "sqlite3:./file.db" do |db|
2019-07-02 13:01:17 +00:00
# # When using the pg driver, use $1, $2, etc. instead of ?
2017-11-13 13:56:33 +00:00
# db.exec "create table contacts (name text, age integer)"
2016-01-31 22:40:02 +00:00
# db.exec "insert into contacts values (?, ?)", "John Doe", 30
2016-02-04 00:28:53 +00:00
#
# args = [] of DB::Any
# args << "Sarah"
# args << 33
2020-01-14 13:29:54 +00:00
# db.exec "insert into contacts values (?, ?)", args: args
2016-01-31 22:40:02 +00:00
#
# 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
# ```
#
2016-01-28 22:41:12 +00:00
module DB
2020-09-25 17:49:50 +00:00
Log = :: Log . for ( self )
2016-01-31 22:40:02 +00:00
# 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
2016-12-14 18:17:40 +00:00
TYPES = [ Nil , String , Bool , Int32 , Int64 , Float32 , Float64 , Time , Bytes ]
2016-01-31 22:40:02 +00:00
2016-06-21 15:08:51 +00:00
# See `DB::TYPES` in `DB`. `Any` is a union of all types in `DB::TYPES`
{% begin %}
alias Any = Union ( {{ * TYPES }} )
{% end %}
2016-01-29 19:13:01 +00:00
2016-02-02 02:02:04 +00:00
# Result of a `#exec` statement.
2016-06-21 21:31:13 +00:00
record ExecResult , rows_affected : Int64 , last_insert_id : Int64
2016-02-02 00:55:30 +00:00
2016-02-02 02:02:04 +00:00
# :nodoc:
2016-02-19 21:42:30 +00:00
def self . driver_class ( driver_name ) : Driver . class
2016-10-21 14:24:32 +00:00
drivers [ driver_name ]? ||
raise ( ArgumentError . new ( %( no driver was registered for the schema " #{ driver_name } ", did you maybe forget to require the database driver? ) ) )
2016-01-28 22:41:12 +00:00
end
2016-02-03 21:29:09 +00:00
# Registers a driver class for a given *driver_name*.
2016-01-31 22:40:02 +00:00
# Should be called by drivers implementors only.
def self . register_driver ( driver_name , driver_class : Driver . class )
2016-10-21 14:24:32 +00:00
drivers [ driver_name ] = driver_class
end
private def self . drivers
2016-01-28 22:41:12 +00:00
@@drivers || = { } of String = > Driver . class
end
2019-07-02 13:01:17 +00:00
# Creates a `Database` pool and opens initial connection(s) as specified in the connection *uri*.
# Use `DB#connect` to open a single connection.
#
2016-02-03 21:29:09 +00:00
# The scheme of the *uri* determines the driver to use.
2019-07-02 13:01:17 +00:00
# Connection parameters such as hostname, user, database name, etc. are specified according
# to each database driver's specific format.
#
# The returned database must be closed by `Database#close`.
2016-02-03 21:29:09 +00:00
def self . open ( uri : URI | String )
build_database ( uri )
2016-01-28 22:41:12 +00:00
end
2016-01-30 22:46:43 +00:00
2019-07-02 13:01:17 +00:00
# Same as `#open` but the database is yielded and closed automatically at the end of the block.
2016-02-03 21:29:09 +00:00
def self . open ( uri : URI | String , & block )
2016-06-29 19:54:16 +00:00
db = build_database ( uri )
begin
yield db
ensure
db . close
2016-01-30 22:46:43 +00:00
end
end
2016-02-03 21:29:09 +00:00
2017-03-20 18:08:30 +00:00
# Opens a connection using the specified *uri*.
# The scheme of the *uri* determines the driver to use.
# Returned connection must be closed by `Connection#close`.
# If a block is used the connection is yielded and closed automatically.
def self . connect ( uri : URI | String )
build_connection ( uri )
end
2020-09-25 17:49:50 +00:00
# :ditto:
2017-03-20 18:08:30 +00:00
def self . connect ( uri : URI | String , & block )
cnn = build_connection ( uri )
begin
yield cnn
ensure
cnn . close
end
end
2016-02-03 21:29:09 +00:00
private def self . build_database ( connection_string : String )
build_database ( URI . parse ( connection_string ) )
end
private def self . build_database ( uri : URI )
2023-06-23 01:03:08 +00:00
driver = build_driver ( uri )
params = HTTP :: Params . parse ( uri . query || " " )
connection_options = driver . connection_options ( params )
pool_options = driver . pool_options ( params )
builder = driver . connection_builder ( uri )
factory = - > { builder . build }
Database . new ( connection_options , pool_options , & factory )
2017-03-20 18:08:30 +00:00
end
private def self . build_connection ( connection_string : String )
build_connection ( URI . parse ( connection_string ) )
end
private def self . build_connection ( uri : URI )
2023-06-23 01:03:08 +00:00
build_driver ( uri ) . connection_builder ( uri ) . build
2017-03-20 18:08:30 +00:00
end
private def self . build_driver ( uri : URI )
driver_class ( uri . scheme ) . new
2016-02-03 21:29:09 +00:00
end
2016-12-03 01:09:27 +00:00
# :nodoc:
def self . fetch_bool ( params : HTTP :: Params , name , default : Bool )
2016-12-05 13:38:46 +00:00
case ( value = params [ name ]? ) . try & . downcase
when nil
2016-12-03 01:09:27 +00:00
default
2016-12-05 13:38:46 +00:00
when " true "
true
when " false "
false
else
raise ArgumentError . new ( %( invalid " #{ value } " value for option " #{ name } " ) )
2016-12-03 01:09:27 +00:00
end
end
2016-01-28 22:41:12 +00:00
end
2016-07-05 18:21:39 +00:00
require " ./db/pool "
2016-08-30 19:20:18 +00:00
require " ./db/string_key_cache "
2019-09-20 20:23:09 +00:00
require " ./db/enumerable_concat "
2016-02-26 01:37:10 +00:00
require " ./db/query_methods "
2016-12-04 18:14:43 +00:00
require " ./db/session_methods "
2016-02-26 01:37:10 +00:00
require " ./db/disposable "
2023-06-23 01:03:08 +00:00
require " ./db/connection_builder "
2016-02-26 01:37:10 +00:00
require " ./db/driver "
require " ./db/statement "
2016-11-16 02:46:11 +00:00
require " ./db/begin_transaction "
2017-03-20 18:08:30 +00:00
require " ./db/connection_context "
2016-12-04 18:14:43 +00:00
require " ./db/connection "
2016-11-16 02:46:11 +00:00
require " ./db/transaction "
require " ./db/statement "
2016-08-29 19:56:34 +00:00
require " ./db/pool_statement "
2016-12-04 18:14:43 +00:00
require " ./db/database "
2016-12-03 19:03:50 +00:00
require " ./db/pool_prepared_statement "
2016-12-03 18:56:03 +00:00
require " ./db/pool_unprepared_statement "
2016-02-26 01:37:10 +00:00
require " ./db/result_set "
2016-06-28 17:02:08 +00:00
require " ./db/error "
2016-03-28 23:45:07 +00:00
require " ./db/mapping "
2019-10-31 14:34:23 +00:00
require " ./db/serializable "