shard-crystal-sqlite3/src/sqlite3/connection.cr

133 lines
3.2 KiB
Crystal

class SQLite3::Connection < DB::Connection
def initialize(database)
super
filename = self.class.filename(database.uri)
check LibSQLite3.open_v2(filename, out @db, (Flag::READWRITE | Flag::CREATE), nil)
# 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)
process_query_params(database.uri)
rescue
raise DB::ConnectionRefused.new
end
def self.filename(uri : URI)
URI.decode_www_form((uri.host || "") + uri.path)
end
def build_prepared_statement(query) : Statement
Statement.new(self, query)
end
def build_unprepared_statement(query) : Statement
# 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
def do_close
super
check LibSQLite3.close(self)
end
# :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
# 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
def to_unsafe
@db
end
private def check(code)
raise Exception.new(self) unless code == 0
end
private def process_query_params(uri : URI)
return unless query = uri.query
detected_pragmas = extract_params(query,
busy_timeout: nil,
cache_size: nil,
foreign_keys: nil,
journal_mode: nil,
synchronous: nil,
wal_autocheckpoint: nil,
)
# concatenate all into a single SQL string
sql = String.build do |str|
detected_pragmas.each do |key, value|
next unless value
str << "PRAGMA #{key}=#{value};"
end
end
check LibSQLite3.exec(@db, sql, nil, nil, nil)
end
private def extract_params(query : String, **default : **T) forall T
res = default
URI::Params.parse(query) do |key, value|
{% begin %}
case key
{% for key in T %}
when {{ key.stringify }}
res = res.merge({{key.id}}: value)
{% end %}
end
{% end %}
end
res
end
end