2016-01-31 00:14:46 +00:00
|
|
|
class SQLite3::Connection < DB::Connection
|
2023-06-23 18:36:08 +00:00
|
|
|
record Options,
|
|
|
|
filename : String = ":memory:",
|
|
|
|
# pragmas
|
|
|
|
busy_timeout : String? = nil,
|
|
|
|
cache_size : String? = nil,
|
|
|
|
foreign_keys : String? = nil,
|
|
|
|
journal_mode : String? = nil,
|
|
|
|
synchronous : String? = nil,
|
|
|
|
wal_autocheckpoint : String? = nil do
|
|
|
|
def self.from_uri(uri : URI, default = Options.new)
|
|
|
|
params = HTTP::Params.parse(uri.query || "")
|
|
|
|
|
|
|
|
Options.new(
|
|
|
|
filename: URI.decode_www_form((uri.host || "") + uri.path),
|
|
|
|
# pragmas
|
|
|
|
busy_timeout: params.fetch("busy_timeout", default.busy_timeout),
|
|
|
|
cache_size: params.fetch("cache_size", default.cache_size),
|
|
|
|
foreign_keys: params.fetch("foreign_keys", default.foreign_keys),
|
|
|
|
journal_mode: params.fetch("journal_mode", default.journal_mode),
|
|
|
|
synchronous: params.fetch("synchronous", default.synchronous),
|
|
|
|
wal_autocheckpoint: params.fetch("wal_autocheckpoint", default.wal_autocheckpoint),
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def pragma_statement
|
|
|
|
res = String.build do |str|
|
|
|
|
pragma_append(str, "busy_timeout", busy_timeout)
|
|
|
|
pragma_append(str, "cache_size", cache_size)
|
|
|
|
pragma_append(str, "foreign_keys", foreign_keys)
|
|
|
|
pragma_append(str, "journal_mode", journal_mode)
|
|
|
|
pragma_append(str, "synchronous", synchronous)
|
|
|
|
pragma_append(str, "wal_autocheckpoint", wal_autocheckpoint)
|
|
|
|
end
|
|
|
|
|
|
|
|
res.empty? ? nil : res
|
|
|
|
end
|
|
|
|
|
|
|
|
private def pragma_append(io, key, value)
|
|
|
|
return unless value
|
|
|
|
io << "PRAGMA #{key}=#{value};"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(options : ::DB::Connection::Options, sqlite3_options : Options)
|
|
|
|
super(options)
|
|
|
|
check LibSQLite3.open_v2(sqlite3_options.filename, out @db, (Flag::READWRITE | Flag::CREATE), nil)
|
2020-11-11 18:00:40 +00:00
|
|
|
# 2 means 2 arguments; 1 is the code for UTF-8
|
|
|
|
check LibSQLite3.create_function(@db, "regexp", 2, 1, nil, SQLite3::REGEXP_FN, nil, nil)
|
2023-02-18 20:06:09 +00:00
|
|
|
|
2023-06-23 18:36:08 +00:00
|
|
|
if pragma_statement = sqlite3_options.pragma_statement
|
|
|
|
check LibSQLite3.exec(@db, pragma_statement, nil, nil, nil)
|
|
|
|
end
|
2016-12-15 17:49:39 +00:00
|
|
|
rescue
|
|
|
|
raise DB::ConnectionRefused.new
|
2016-01-31 00:14:46 +00:00
|
|
|
end
|
|
|
|
|
2016-02-04 00:29:19 +00:00
|
|
|
def self.filename(uri : URI)
|
2020-09-30 14:34:25 +00:00
|
|
|
URI.decode_www_form((uri.host || "") + uri.path)
|
2016-01-31 00:14:46 +00:00
|
|
|
end
|
|
|
|
|
2019-07-29 13:25:46 +00:00
|
|
|
def build_prepared_statement(query) : Statement
|
2016-02-04 00:52:45 +00:00
|
|
|
Statement.new(self, query)
|
2016-01-31 00:14:46 +00:00
|
|
|
end
|
|
|
|
|
2019-07-29 13:25:46 +00:00
|
|
|
def build_unprepared_statement(query) : Statement
|
2016-12-03 20:52:24 +00:00
|
|
|
# sqlite3 does not support unprepared statement.
|
|
|
|
# All statements once prepared should be released
|
|
|
|
# when unneeded. Unprepared statement are not aim
|
|
|
|
# to leave state in the connection. Mimicking them
|
|
|
|
# with prepared statement would be wrong with
|
|
|
|
# respect connection resources.
|
|
|
|
raise DB::Error.new("SQLite3 driver does not support unprepared statements")
|
|
|
|
end
|
|
|
|
|
2016-02-04 00:29:19 +00:00
|
|
|
def do_close
|
|
|
|
super
|
2017-05-29 18:20:32 +00:00
|
|
|
check LibSQLite3.close(self)
|
2016-01-31 17:03:05 +00:00
|
|
|
end
|
|
|
|
|
2016-12-14 15:27:43 +00:00
|
|
|
# :nodoc:
|
|
|
|
def perform_begin_transaction
|
|
|
|
self.prepared.exec "BEGIN"
|
|
|
|
end
|
|
|
|
|
|
|
|
# :nodoc:
|
|
|
|
def perform_commit_transaction
|
|
|
|
self.prepared.exec "COMMIT"
|
|
|
|
end
|
|
|
|
|
|
|
|
# :nodoc:
|
|
|
|
def perform_rollback_transaction
|
|
|
|
self.prepared.exec "ROLLBACK"
|
|
|
|
end
|
|
|
|
|
|
|
|
# :nodoc:
|
|
|
|
def perform_create_savepoint(name)
|
|
|
|
self.prepared.exec "SAVEPOINT #{name}"
|
|
|
|
end
|
|
|
|
|
|
|
|
# :nodoc:
|
|
|
|
def perform_release_savepoint(name)
|
|
|
|
self.prepared.exec "RELEASE SAVEPOINT #{name}"
|
|
|
|
end
|
|
|
|
|
|
|
|
# :nodoc:
|
|
|
|
def perform_rollback_savepoint(name)
|
|
|
|
self.prepared.exec "ROLLBACK TO #{name}"
|
|
|
|
end
|
|
|
|
|
2016-06-23 20:27:58 +00:00
|
|
|
# Dump the database to another SQLite3 database. This can be used for backing up a SQLite3 Database
|
|
|
|
# to disk or the opposite
|
|
|
|
def dump(to : SQLite3::Connection)
|
|
|
|
backup_item = LibSQLite3.backup_init(to.@db, "main", @db, "main")
|
|
|
|
if backup_item.null?
|
|
|
|
raise Exception.new(to.@db)
|
|
|
|
end
|
|
|
|
code = LibSQLite3.backup_step(backup_item, -1)
|
|
|
|
|
|
|
|
if code != LibSQLite3::Code::DONE
|
|
|
|
raise Exception.new(to.@db)
|
|
|
|
end
|
|
|
|
code = LibSQLite3.backup_finish(backup_item)
|
|
|
|
if code != LibSQLite3::Code::OKAY
|
|
|
|
raise Exception.new(to.@db)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-01-31 00:14:46 +00:00
|
|
|
def to_unsafe
|
|
|
|
@db
|
|
|
|
end
|
|
|
|
|
|
|
|
private def check(code)
|
|
|
|
raise Exception.new(self) unless code == 0
|
|
|
|
end
|
|
|
|
end
|