lib/litedb.rb in litedb-0.1.0 vs lib/litedb.rb in litedb-0.2.0

- old
+ new

@@ -1,8 +1,100 @@ # frozen_string_literal: true require_relative "litedb/version" -module Litedb +require "sqlite3" +require "litescheduler" + +class Litedb < SQLite3::Database class Error < StandardError; end - # Your code goes here... + + DEFAULT_FILE = ":memory:" + DEFAULT_OPTIONS = { + synchronous: :NORMAL, + mmap_size: 32 * 1024, # 32 kilobytes + journal_size_limit: 64 * 1024 * 1024 # 64 megabytes + }.freeze + + attr_reader :config + + def initialize(file = nil, options = {}, zvfs = nil) + @scheduler = Litescheduler.instance + file ||= DEFAULT_FILE + options = options.transform_keys { |key| + begin + key.to_sym + rescue + key + end + } + @config = DEFAULT_OPTIONS.merge(options) + + super(file, @config, zvfs) + + set_pragmas + run_migrations + prepare_statements + end + + def run_statement(statement, *args) + statements[statement].execute!(*args) + end + + private + + def set_pragmas + # set a custom busy handler to override the `busy_timeout` + # this ensures we either switch to another execution context or try again to connect + # https://www.sqlite.org/pragma.html#pragma_busy_timeout + busy_handler { @scheduler.switch || sleep(rand * 0.001) } + # Journal mode WAL allows for greater concurrency (many readers + one writer) + # https://www.sqlite.org/pragma.html#pragma_journal_mode + self.journal_mode = :WAL + # level of database durability + # 2 = "FULL" (sync on every write) + # 1 = "NORMAL" (sync every 1000 written pages) + # 0 = "OFF" (don't sync) + # https://www.sqlite.org/pragma.html#pragma_synchronous + self.synchronous = @config[:synchronous] + # set the global memory map so all processes can share data + # https://www.sqlite.org/pragma.html#pragma_mmap_size + # https://www.sqlite.org/mmap.html + self.mmap_size = @config[:mmap_size] + # impose a limit on the WAL file to prevent unlimited growth (with a negative impact on read performance as well) + # https://www.sqlite.org/pragma.html#pragma_journal_size_limit + self.journal_size_limit = @config[:journal_size_limit] + end + + def run_migrations + return if @config[:migrations].nil? + + migrations = if @config[:migrations].is_a?(Hash) + @config[:migrations].values + elsif @config[:migrations].is_a?(Array) + @config[:migrations] + else + raise Error.new("`migrations` option must be either a Hash or an Array") + end + + transaction(:immediate) do + migrations.each do |sql| + execute(clean_sql(sql)) + end + end + end + + def prepare_statements + instance_variable_set(:@statements, {}) + self.class.attr_reader(:statements) unless respond_to?(:statements) + + return if @config[:statements].nil? + + @config[:statements].each do |key, sql| + statements[key.to_sym] = prepare(clean_sql(sql)) + end + end + + def clean_sql(sql) + sql.gsub(/[[:space:]]+/, " ").strip + end end