-
-
Notifications
You must be signed in to change notification settings - Fork 54
/
db.cr
187 lines (170 loc) · 5.31 KB
/
db.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
require "uri"
# The DB module is a unified interface to database access.
# Database dialects is supported by custom database driver shards.
# Check [crystal-lang/crystal-sqlite3](https://github.com/crystal-lang/crystal-sqlite3) for example.
#
# Drivers implementors check `Driver` class.
#
# DB manage a connection pool. The connection pool can be configured by `URI` query. See `Database`.
#
# ### Usage
#
# Assuming `crystal-sqlite3` is included a sqlite3 database can be opened with `#open`.
#
# ```
# db = DB.open "sqlite3:./path/to/db/file.db"
# db.close
# ```
#
# If a block is given to `#open` the database is closed automatically
#
# ```
# DB.open "sqlite3:./file.db" do |db|
# # work with db
# end # db is closed
# ```
#
# In the code above `db` is a `Database`. Methods available for querying it are described in `QueryMethods`.
#
# 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:./file.db" do |db|
# db.exec "create table contacts (name string, age integer)"
# db.exec "insert into contacts values (?, ?)", "John Doe", 30
#
# args = [] of DB::Any
# args << "Sarah"
# args << 33
# db.exec "insert into contacts values (?, ?)", args
#
# 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
# 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 = [Nil, String, Bool, Int32, Int64, Float32, Float64, Time, Bytes]
# See `DB::TYPES` in `DB`. `Any` is a union of all types in `DB::TYPES`
{% begin %}
alias Any = Union({{*TYPES}})
{% end %}
# Result of a `#exec` statement.
record ExecResult, rows_affected : Int64, last_insert_id : Int64
# :nodoc:
def self.driver_class(driver_name) : Driver.class
drivers[driver_name]? ||
raise(ArgumentError.new(%(no driver was registered for the schema "#{driver_name}", did you maybe forget to require the database driver?)))
end
# 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[driver_name] = driver_class
end
private def self.drivers
@@drivers ||= {} of String => Driver.class
end
# Opens a database using the specified *uri*.
# The scheme of the *uri* determines the driver to use.
# Returned database must be closed by `Database#close`.
# If a block is used the database is yielded and closed automatically.
def self.open(uri : URI | String)
build_database(uri)
end
# Same as `#open` but the database is yielded and closed automatically.
def self.open(uri : URI | String, &block)
db = build_database(uri)
begin
yield db
ensure
db.close
end
end
# 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
# ditto
def self.connect(uri : URI | String, &block)
cnn = build_connection(uri)
begin
yield cnn
ensure
cnn.close
end
end
private def self.build_database(connection_string : String)
build_database(URI.parse(connection_string))
end
private def self.build_database(uri : URI)
Database.new(build_driver(uri), uri)
end
private def self.build_connection(connection_string : String)
build_connection(URI.parse(connection_string))
end
private def self.build_connection(uri : URI)
build_driver(uri).build_connection(SingleConnectionContext.new(uri)).as(Connection)
end
private def self.build_driver(uri : URI)
driver_class(uri.scheme).new
end
# :nodoc:
def self.fetch_bool(params : HTTP::Params, name, default : Bool)
case (value = params[name]?).try &.downcase
when nil
default
when "true"
true
when "false"
false
else
raise ArgumentError.new(%(invalid "#{value}" value for option "#{name}"))
end
end
end
require "./db/pool"
require "./db/string_key_cache"
require "./db/query_methods"
require "./db/session_methods"
require "./db/disposable"
require "./db/driver"
require "./db/statement"
require "./db/begin_transaction"
require "./db/connection_context"
require "./db/connection"
require "./db/transaction"
require "./db/statement"
require "./db/pool_statement"
require "./db/database"
require "./db/pool_prepared_statement"
require "./db/pool_unprepared_statement"
require "./db/result_set"
require "./db/error"
require "./db/mapping"