# SQLite3 wrapper # Copyright (C) 2023 Nguyễn Gia Phong # # This file if part of hybring. # # Hybring is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Hybring is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with hybring. If not, see . @[Link("sqlite3")] lib SQLite OK = 0 ROW = 100 DONE = 101 type Database = Void* type Statement = Void* fun errstr = sqlite3_errstr(rc : LibC::Int) : LibC::Char* fun open = sqlite3_open(filename : LibC::Char*, db : Database*) : LibC::Int fun prepare = sqlite3_prepare(db : Database, query : LibC::Char*, length : LibC::Int, stmt : Statement*, query_tail : LibC::Char**) : LibC::Int fun step = sqlite3_step(stmt : Statement) : Int32 fun column_text = sqlite3_column_text(stmt : Statement, col : LibC::Int) : LibC::Char* fun column_int = sqlite3_column_int(stmt : Statement, col : LibC::Int) : LibC::Int fun finalize = sqlite3_finalize(stmt : Statement) : LibC::Int fun close = sqlite3_close(db : Database) : LibC::Int end class Database MIGRATIONS = [""] # migration #0 is reserved for schema initialization SCHEMA = "CREATE TABLE member ( id INTEGER PRIMARY KEY, nick TEXT NOT NULL UNIQUE, opennic TEXT NOT NULL UNIQUE, icann TEXT NOT NULL UNIQUE)"; class Statement def initialize(db, query) bytes = query.to_slice Database.check SQLite.prepare db.ref, bytes, bytes.size, out @ref, out @tail end def ref @ref end def step : LibC::Int SQLite.step @ref end def finalize Database.check SQLite.finalize @ref end end class Column def initialize(stmt : SQLite::Statement, i : LibC::Int) @stmt = stmt @i = i end def int SQLite.column_int @stmt, @i end def text String.new SQLite.column_text @stmt, @i end end class Row def initialize(stmt : SQLite::Statement) @stmt = stmt end def [](i : LibC::Int) Column.new @stmt, i end end def initialize(path : String) Database.check SQLite.open path, out @ref self.exec "PRAGMA user_version" do |row| version = row[0].int raise "negative schema version" if version < 0 raise "schema version newer than supported" if version > MIGRATIONS.size if version == 0 self.exec SCHEMA do end self.exec "PRAGMA user_version = #{MIGRATIONS.size}" do end end end end def ref @ref end def exec(query : String) stmt = Statement.new self, query loop do rc = stmt.step case rc when SQLite::ROW yield Row.new stmt.ref when SQLite::DONE break else Database.check rc end end end def finalize Database.check SQLite.close @ref end end def Database.check(rc : LibC::Int) raise String.new SQLite.errstr rc if rc != SQLite::OK end