class SlugDB

Zero dependecy NoSQL, file based database

Constants

VERSION

Public Class Methods

new(file, thread_safe: false, ultra_safe: false) click to toggle source
# File lib/slugdb.rb, line 9
def initialize(file, thread_safe: false, ultra_safe: false)
  @pstore = PStore.new(file, thread_safe).tap { |s| s.ultra_safe = ultra_safe }
  @pstore.transaction do |db|
    db[:main] ||= {}
    db[:indexes] ||= {}
  end
end

Public Instance Methods

add_index(name:, pk:, sk:) click to toggle source
# File lib/slugdb.rb, line 32
def add_index(name:, pk:, sk:) # rubocop:disable Naming/MethodParameterName
  @pstore.transaction do |db|
    db[:indexes] ||= {}
    db[:indexes][name] = { pk: pk, sk: sk }
  end
  reindex!

  { name => { pk: pk, sk: sk } }
end
delete_item(pk:, sk:, **_) click to toggle source

rubocop:disable Naming/MethodParameterName

# File lib/slugdb.rb, line 75
def delete_item(pk:, sk:, **_)
  item = get_item(pk: pk, sk: sk)
  return if item.nil?

  indexes = list_indexes
  @pstore.transaction { |db| perform_delete(db, indexes, pk, sk, item) }

  item
end
get_item(pk:, sk:, **_) click to toggle source
# File lib/slugdb.rb, line 50
def get_item(pk:, sk:, **_) # rubocop:disable Naming/MethodParameterName
  @pstore.transaction do |db|
    next if db[:main][pk].nil? || db[:main][pk][sk].nil?

    db[:main][pk][sk]
  end
end
list_indexes() click to toggle source
# File lib/slugdb.rb, line 42
def list_indexes
  @pstore.transaction { |db| db[:indexes] }
end
list_partitions() click to toggle source
# File lib/slugdb.rb, line 46
def list_partitions
  @pstore.transaction { |db| db[:main].keys }
end
put_item(pk:, sk:, **attributes) click to toggle source
# File lib/slugdb.rb, line 58
def put_item(pk:, sk:, **attributes) # rubocop:disable Naming/MethodParameterName
  old_item = get_item(pk: pk, sk: sk)
  new_item = attributes.merge(pk: pk, sk: sk)
  indexes = list_indexes

  @pstore.transaction do |db|
    perform_delete(db, indexes, pk, sk, old_item)

    db[:main][pk] ||= {}
    db[:main][pk][sk] = new_item
    indexes.each { |name, schema| index_item(db, new_item, name, schema) }
  end

  new_item
end
query(pk:, index: :main, select: ->(sk) { true } click to toggle source

rubocop:disable Lint/UnusedBlockArgument,Naming/MethodParameterName rubocop:disable Metrics/PerceivedComplexity,Metrics/MethodLength rubocop:disable Metrics/CyclomaticComplexity,Metrics/AbcSize,

# File lib/slugdb.rb, line 89
def query(pk:, index: :main, select: ->(sk) { true }, filter: ->(item) { true })
  if index == :main
    @pstore.transaction do |db|
      db[index]
        .fetch(pk, {})
        .map { |_, records| records }
        .select { |item| select[item[:sk]] }
        .filter { |item| filter[item] }
    end
  else
    name, schema = list_indexes.find { |name,| name == index }
    @pstore.transaction do |db|
      db[name]
        .fetch(pk, {})
        .map do |_, isk_records|
          isk_records.map do |_, pk_records|
            pk_records.map { |_, sk_records| sk_records }
          end
        end # rubocop:disable Style/MultilineBlockChain
        .flatten(2)
        .select { |item| select[item[schema[:sk]]] }
        .filter { |item| filter[item] }
    end
  end
end
reindex!() click to toggle source
# File lib/slugdb.rb, line 17
def reindex!
  indexes = list_indexes
  @pstore.transaction { |db| db[:main] }.each do |pk, records|
    records.each do |sk, record|
      @pstore.transaction do |db|
        indexes.each do |name, schema|
          index_item(db, record.merge(pk: pk, sk: sk), name, schema)
        end
      end
    end
  end

  nil
end

Private Instance Methods

delete_if_empty?(hash, key) click to toggle source
# File lib/slugdb.rb, line 146
def delete_if_empty?(hash, key)
  hash.delete(key) if hash[key].empty?
end
index_item(db, item, name, schema) click to toggle source

rubocop:enable Lint/UnusedBlockArgument,Naming/MethodParameterName rubocop:enable Metrics/PerceivedComplexity,Metrics/MethodLength rubocop:enable Metrics/CyclomaticComplexity,Metrics/AbcSize

# File lib/slugdb.rb, line 120
def index_item(db, item, name, schema) # rubocop:disable Metrics/AbcSize
  return unless item.key?(schema[:pk]) && item.key?(schema[:sk])

  db[name] ||= {}
  db[name][item[schema[:pk]]] ||= {}
  db[name][item[schema[:pk]]][item[schema[:sk]]] ||= {}
  db[name][item[schema[:pk]]][item[schema[:sk]]][item[:pk]] ||= {}
  db[name][item[schema[:pk]]][item[schema[:sk]]][item[:pk]][item[:sk]] = item
end
perform_delete(db, indexes, pk, sk, item) click to toggle source
# File lib/slugdb.rb, line 130
def perform_delete(db, indexes, pk, sk, item) # rubocop:disable Naming/MethodParameterName,Metrics/AbcSize
  return if item.nil?

  db[:main][pk].delete(sk)
  db[:main].delete(pk) if db[:main][pk].empty?

  indexes.each do |name, schema|
    next unless item.key?(schema[:pk]) && item.key?(schema[:sk])

    db[name][item[schema[:pk]]][item[schema[:sk]]][item[:pk]].delete(item[:sk])
    delete_if_empty?(db[name][item[schema[:pk]]][item[schema[:sk]]], item[:pk])
    delete_if_empty?(db[name][item[schema[:pk]]], item[schema[:sk]])
    delete_if_empty?(db[name], item[schema[:pk]])
  end
end