950 lines
21 KiB
Crystal
950 lines
21 KiB
Crystal
|
require "spec"
|
|||
|
require "file_utils"
|
|||
|
require "./db-ships.cr"
|
|||
|
|
|||
|
def fork_process(&)
|
|||
|
Process.new Crystal::System::Process.fork { yield }
|
|||
|
end
|
|||
|
|
|||
|
describe "DODB::Storage::Uncached" do
|
|||
|
describe "basics" do
|
|||
|
it "store and get data" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
db.to_a.sort.should eq(Ship.all_ships.sort)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "rewrite already stored data" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
ship = Ship.all_ships[0]
|
|||
|
|
|||
|
key = db << ship
|
|||
|
|
|||
|
db[key] = Ship.new "broken"
|
|||
|
db[key] = ship
|
|||
|
|
|||
|
db[key].should eq(ship)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "properly remove data" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db.pop
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each_with_index do |ship, i|
|
|||
|
# FIXME: Should it raise a particular exception?
|
|||
|
expect_raises DODB::MissingEntry do
|
|||
|
db[i]
|
|||
|
end
|
|||
|
|
|||
|
db[i]?.should be_nil
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "preserves data on reopening" do
|
|||
|
db1 = SPECDB::Uncached(Ship).new
|
|||
|
db1 << Ship.kisaragi
|
|||
|
|
|||
|
db1.to_a.size.should eq(1)
|
|||
|
|
|||
|
db2 = SPECDB::Uncached(Ship).new remove_previous_data: false
|
|||
|
db2 << Ship.mutsuki
|
|||
|
|
|||
|
db1.to_a.size.should eq(2)
|
|||
|
|
|||
|
db1.rm_storage_dir
|
|||
|
db2.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "iterates in normal and reversed order" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
# The two #each test iteration.
|
|||
|
db.each_with_key do |item, index|
|
|||
|
item.should eq Ship.all_ships[index]
|
|||
|
end
|
|||
|
|
|||
|
db.each_with_key(reversed: true) do |item, index|
|
|||
|
item.should eq Ship.all_ships[index]
|
|||
|
end
|
|||
|
|
|||
|
# Actual reversal is tested here.
|
|||
|
db.to_a(reversed: true).should eq db.to_a.reverse
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "respects the provided offsets if any" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
db.to_a(offset: 0, limit: 1)[0]?.should eq Ship.mutsuki
|
|||
|
db.to_a(offset: 1, limit: 1)[0]?.should eq Ship.kisaragi
|
|||
|
db.to_a(offset: 2, limit: 1)[0]?.should eq Ship.yayoi
|
|||
|
|
|||
|
db.to_a(offset: 0, limit: 3).should eq [
|
|||
|
Ship.mutsuki, Ship.kisaragi, Ship.yayoi
|
|||
|
]
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "indices" do
|
|||
|
it "do basic indexing" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each_with_index do |ship|
|
|||
|
db_ships_by_name.get?(ship.name).should eq(ship)
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "raise on index overload" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
db << Ship.kisaragi
|
|||
|
|
|||
|
# Should not be allowed to store an entry whose “name” field
|
|||
|
# already exists.
|
|||
|
expect_raises(DODB::IndexOverload) do
|
|||
|
db << Ship.kisaragi
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "properly deindex" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each_with_index do |ship, i|
|
|||
|
db.delete i
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db_ships_by_name.get?(ship.name).should be_nil
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "properly reindex" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
key = db << Ship.kisaragi
|
|||
|
|
|||
|
# We give the old id to the new ship, to get it replaced in
|
|||
|
# the database.
|
|||
|
some_new_ship = Ship.all_ships[2].clone
|
|||
|
|
|||
|
db[key] = some_new_ship
|
|||
|
|
|||
|
db[key].should eq(some_new_ship)
|
|||
|
|
|||
|
db_ships_by_name.get?(some_new_ship.name).should eq(some_new_ship)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "properly updates" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
new_kisaragi = Ship.kisaragi.clone.tap do |s|
|
|||
|
s.name = "Kisaragi Kai" # Don’t think about it too much.
|
|||
|
end
|
|||
|
|
|||
|
# We’re changing an indexed value on purpose.
|
|||
|
db_ships_by_name.update "Kisaragi", new_kisaragi
|
|||
|
|
|||
|
db_ships_by_name.get?("Kisaragi").should be_nil
|
|||
|
db_ships_by_name.get?(new_kisaragi.name).should eq new_kisaragi
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "partitions" do
|
|||
|
it "do basic partitioning" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_class = db.new_partition "class", &.klass
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db_ships_by_class.get(ship.klass).should contain(ship)
|
|||
|
end
|
|||
|
|
|||
|
# We extract the possible classes to do test on them.
|
|||
|
ship_classes = Ship.all_ships.map(&.klass).uniq
|
|||
|
ship_classes.each do |klass|
|
|||
|
partition = db_ships_by_class.get klass
|
|||
|
|
|||
|
# A partition on “class” should contain entries that all
|
|||
|
# share the same value of “class”.
|
|||
|
partition.map(&.klass.==(klass)).reduce { |a, b|
|
|||
|
a && b
|
|||
|
}.should be_true
|
|||
|
end
|
|||
|
|
|||
|
db_ships_by_class.get?("does-not-exist").should eq([] of Ship)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "removes select elements from partitions" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_class = db.new_partition "class", &.klass
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
db_ships_by_class.delete "Mutsuki", &.name.==("Kisaragi")
|
|||
|
|
|||
|
Ship.all_ships.map(&.klass).uniq.each do |klass|
|
|||
|
partition = db_ships_by_class.get klass
|
|||
|
|
|||
|
partition.any?(&.name.==("Kisaragi")).should be_false
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "tags" do
|
|||
|
it "do basic tagging" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
db_ships_by_tags.get("flagship").should eq([Ship.flagship])
|
|||
|
|
|||
|
# All returned entries should have the requested tag.
|
|||
|
db_ships_by_tags.get("name ship")
|
|||
|
.map(&.tags.includes?("name ship"))
|
|||
|
.reduce { |a, e| a && e }
|
|||
|
.should be_true
|
|||
|
|
|||
|
# There shouldn’t be one in our data about WWII Japanese warships…
|
|||
|
db_ships_by_tags.get?("starship").should eq([] of Ship)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "properly removes tags" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
# Removing the “flagship” tag, brace for impact.
|
|||
|
flagship, index = db_ships_by_tags.get_with_keys("flagship")[0]
|
|||
|
flagship.tags = [] of String
|
|||
|
db[index] = flagship
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# ship, index = db_ships_by_tags.update(tag: "flagship") do |ship, index|
|
|||
|
# ship.tags = [] of String
|
|||
|
# db[index] = ship
|
|||
|
# end
|
|||
|
|
|||
|
db_ships_by_tags.get("flagship").should eq([] of Ship)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "gets items that have multiple tags" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
results = db_ships_by_tags.get(["flagship", "name ship"])
|
|||
|
results.should eq([Ship.yamato])
|
|||
|
|
|||
|
results = db_ships_by_tags.get(["name ship", "flagship"])
|
|||
|
results.should eq([Ship.yamato])
|
|||
|
|
|||
|
results = db_ships_by_tags.get(["flagship"])
|
|||
|
results.should eq([Ship.yamato])
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "atomic operations" do
|
|||
|
it "safe_get and safe_get?" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db_ships_by_name.safe_get ship.name do |results|
|
|||
|
results.should eq(ship)
|
|||
|
end
|
|||
|
|
|||
|
db_ships_by_name.safe_get? ship.name do |results|
|
|||
|
results.should eq(ship)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "tools" do
|
|||
|
it "rebuilds indexes" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
db_ships_by_class = db.new_partition "class", &.klass
|
|||
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
db.reindex_everything!
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db_ships_by_name.get?(ship.name).should eq(ship)
|
|||
|
db_ships_by_class.get(ship.klass).should contain(ship)
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "migrates properly" do
|
|||
|
old_db = SPECDB::Uncached(PrimitiveShip).new "-migration-origin"
|
|||
|
|
|||
|
old_ships_by_name = old_db.new_index "name", &.name
|
|||
|
old_ships_by_class = old_db.new_partition "class", &.class_name
|
|||
|
|
|||
|
PrimitiveShip.all_ships.each do |ship|
|
|||
|
old_db << ship
|
|||
|
end
|
|||
|
|
|||
|
# At this point, the “old” DB is filled. Now we need to convert
|
|||
|
# to the new DB.
|
|||
|
|
|||
|
new_db = SPECDB::Uncached(Ship).new "-migration-target"
|
|||
|
|
|||
|
new_ships_by_name = new_db.new_index "name", &.name
|
|||
|
new_ships_by_class = new_db.new_partition "class", &.klass
|
|||
|
new_ships_by_tags = new_db.new_tags "tags", &.tags
|
|||
|
|
|||
|
old_db.each_with_key do |ship, index|
|
|||
|
new_ship = Ship.new ship.name,
|
|||
|
klass: ship.class_name,
|
|||
|
id: ship.id,
|
|||
|
tags: Array(String).new.tap { |tags|
|
|||
|
tags << "name ship" if ship.name == ship.class_name
|
|||
|
}
|
|||
|
|
|||
|
new_db[index] = new_ship
|
|||
|
end
|
|||
|
|
|||
|
# At this point, the conversion is done, so… we’re making a few
|
|||
|
# arbitrary tests on the new data.
|
|||
|
|
|||
|
old_db.each_with_key do |old_ship, old_index|
|
|||
|
ship = new_db[old_index]
|
|||
|
|
|||
|
ship.id.should eq(old_ship.id)
|
|||
|
ship.klass.should eq(old_ship.class_name)
|
|||
|
|
|||
|
ship.tags.any?(&.==("name ship")).should be_true if ship.name == ship.klass
|
|||
|
end
|
|||
|
|
|||
|
old_db.rm_storage_dir
|
|||
|
new_db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "parallel support" do
|
|||
|
# Not sure how many forks would be safe in a test like that.
|
|||
|
fork_count = 25
|
|||
|
entries_per_fork = 100
|
|||
|
|
|||
|
it "works for pushing values" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
|
|||
|
processes = [] of Process
|
|||
|
|
|||
|
fork_count.times do |fork_id|
|
|||
|
processes << fork_process do
|
|||
|
entries_per_fork.times do |entry_id|
|
|||
|
db << Ship.new("entry-#{fork_id}-#{entry_id}", "???")
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
processes.each &.wait
|
|||
|
|
|||
|
dump = db.to_a
|
|||
|
|
|||
|
dump.size.should eq fork_count * entries_per_fork
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "works for updating values" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
db_entries_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
# First pass, creating data.
|
|||
|
processes = [] of Process
|
|||
|
fork_count.times do |fork_id|
|
|||
|
processes << fork_process do
|
|||
|
entries_per_fork.times do |entry_id|
|
|||
|
db << Ship.new("entry-#{fork_id}-#{entry_id}", "???")
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
processes.each &.wait
|
|||
|
|
|||
|
# Second pass, updating data.
|
|||
|
processes = [] of Process
|
|||
|
fork_count.times do |fork_id|
|
|||
|
processes << fork_process do
|
|||
|
entries_per_fork.times do |entry_id|
|
|||
|
db_entries_by_name.update Ship.new("entry-#{fork_id}-#{entry_id}", "???", tags: ["updated"])
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
processes.each &.wait
|
|||
|
|
|||
|
# Third pass, testing database content.
|
|||
|
dump = db.to_a
|
|||
|
|
|||
|
fork_count.times do |fork_id|
|
|||
|
entries_per_fork.times do |entry_id|
|
|||
|
entry = db_entries_by_name.get "entry-#{fork_id}-#{entry_id}"
|
|||
|
|
|||
|
entry.tags.should eq ["updated"]
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "does parallel-safe updates" do
|
|||
|
db = SPECDB::Uncached(Ship).new
|
|||
|
db_entries_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
# We’ll be storing an integer in the "klass" field, and incrementing
|
|||
|
# it in forks in a second time.
|
|||
|
db << Ship.new("test", "0")
|
|||
|
|
|||
|
processes = [] of Process
|
|||
|
fork_count.times do |fork_id|
|
|||
|
processes << fork_process do
|
|||
|
entries_per_fork.times do |entry_id|
|
|||
|
db_entries_by_name.safe_get "test" do |entry|
|
|||
|
entry.klass = (entry.klass.to_i + 1).to_s
|
|||
|
|
|||
|
db_entries_by_name.update "test", entry
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
processes.each &.wait
|
|||
|
|
|||
|
db_entries_by_name.get("test").klass.should eq((fork_count * entries_per_fork).to_s)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
# Basically the same thing as before, with some slight
|
|||
|
# differences based on the fact that changing the on-disk data
|
|||
|
# won't change the cached one.
|
|||
|
describe "DODB::Storage::Cached" do
|
|||
|
describe "basics" do
|
|||
|
it "store and get data" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
db.to_a.sort.should eq(Ship.all_ships.sort)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "rewrite already stored data" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
ship = Ship.all_ships[0]
|
|||
|
|
|||
|
key = db << ship
|
|||
|
|
|||
|
db[key] = Ship.new "broken"
|
|||
|
db[key] = ship
|
|||
|
|
|||
|
db[key].should eq(ship)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "properly remove data" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db.pop
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each_with_index do |ship, i|
|
|||
|
# FIXME: Should it raise a particular exception?
|
|||
|
expect_raises DODB::MissingEntry do
|
|||
|
db[i]
|
|||
|
end
|
|||
|
|
|||
|
db[i]?.should be_nil
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "preserves data on reopening" do
|
|||
|
db1 = SPECDB::Cached(Ship).new
|
|||
|
db1 << Ship.kisaragi
|
|||
|
|
|||
|
db1.to_a.size.should eq(1)
|
|||
|
|
|||
|
db2 = SPECDB::Cached(Ship).new remove_previous_data: false
|
|||
|
db2 << Ship.mutsuki
|
|||
|
|
|||
|
# Only difference with DODB::Storage::Uncached: concurrent DB cannot coexists.
|
|||
|
db2.to_a.size.should eq(2)
|
|||
|
|
|||
|
db1.rm_storage_dir
|
|||
|
db2.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "iterates in normal and reversed order" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
# The two #each test iteration.
|
|||
|
db.each_with_key do |item, index|
|
|||
|
item.should eq Ship.all_ships[index]
|
|||
|
end
|
|||
|
|
|||
|
db.each_with_key(reversed: true) do |item, index|
|
|||
|
item.should eq Ship.all_ships[index]
|
|||
|
end
|
|||
|
|
|||
|
# Actual reversal is tested here.
|
|||
|
db.to_a(reversed: true).should eq db.to_a.reverse
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "respects the provided offsets if any" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
db.to_a(offset: 0, limit: 1)[0]?.should eq Ship.mutsuki
|
|||
|
db.to_a(offset: 1, limit: 1)[0]?.should eq Ship.kisaragi
|
|||
|
db.to_a(offset: 2, limit: 1)[0]?.should eq Ship.yayoi
|
|||
|
|
|||
|
db.to_a(offset: 0, limit: 3).should eq [
|
|||
|
Ship.mutsuki, Ship.kisaragi, Ship.yayoi
|
|||
|
]
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "indices" do
|
|||
|
it "do basic indexing" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each_with_index do |ship|
|
|||
|
db_ships_by_name.get?(ship.name).should eq(ship)
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "raise on index overload" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
db << Ship.kisaragi
|
|||
|
|
|||
|
# Should not be allowed to store an entry whose “name” field
|
|||
|
# already exists.
|
|||
|
expect_raises(DODB::IndexOverload) do
|
|||
|
db << Ship.kisaragi
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "properly deindex" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each_with_index do |ship, i|
|
|||
|
db.delete i
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db_ships_by_name.get?(ship.name).should be_nil
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "properly reindex" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
key = db << Ship.kisaragi
|
|||
|
|
|||
|
# We give the old id to the new ship, to get it replaced in
|
|||
|
# the database.
|
|||
|
some_new_ship = Ship.all_ships[2].clone
|
|||
|
|
|||
|
db[key] = some_new_ship
|
|||
|
|
|||
|
db[key].should eq(some_new_ship)
|
|||
|
|
|||
|
db_ships_by_name.get?(some_new_ship.name).should eq(some_new_ship)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "properly updates" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
new_kisaragi = Ship.kisaragi.clone.tap do |s|
|
|||
|
s.name = "Kisaragi Kai" # Don’t think about it too much.
|
|||
|
end
|
|||
|
|
|||
|
# We’re changing an indexed value on purpose.
|
|||
|
db_ships_by_name.update "Kisaragi", new_kisaragi
|
|||
|
|
|||
|
db_ships_by_name.get?("Kisaragi").should be_nil
|
|||
|
db_ships_by_name.get?(new_kisaragi.name).should eq new_kisaragi
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "partitions" do
|
|||
|
it "do basic partitioning" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_class = db.new_partition "class", &.klass
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db_ships_by_class.get(ship.klass).should contain(ship)
|
|||
|
end
|
|||
|
|
|||
|
# We extract the possible classes to do test on them.
|
|||
|
ship_classes = Ship.all_ships.map(&.klass).uniq
|
|||
|
ship_classes.each do |klass|
|
|||
|
partition = db_ships_by_class.get klass
|
|||
|
|
|||
|
# A partition on “class” should contain entries that all
|
|||
|
# share the same value of “class”.
|
|||
|
partition.map(&.klass.==(klass)).reduce { |a, b|
|
|||
|
a && b
|
|||
|
}.should be_true
|
|||
|
end
|
|||
|
|
|||
|
db_ships_by_class.get?("does-not-exist").should eq([] of Ship)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "removes select elements from partitions" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_class = db.new_partition "class", &.klass
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
db_ships_by_class.delete "Mutsuki", &.name.==("Kisaragi")
|
|||
|
|
|||
|
Ship.all_ships.map(&.klass).uniq.each do |klass|
|
|||
|
partition = db_ships_by_class.get klass
|
|||
|
|
|||
|
partition.any?(&.name.==("Kisaragi")).should be_false
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "tags" do
|
|||
|
it "do basic tagging" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
db_ships_by_tags.get("flagship").should eq([Ship.flagship])
|
|||
|
|
|||
|
# All returned entries should have the requested tag.
|
|||
|
db_ships_by_tags.get("name ship")
|
|||
|
.map(&.tags.includes?("name ship"))
|
|||
|
.reduce { |a, e| a && e }
|
|||
|
.should be_true
|
|||
|
|
|||
|
# There shouldn’t be one in our data about WWII Japanese warships…
|
|||
|
db_ships_by_tags.get?("starship").should eq([] of Ship)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "properly removes tags" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
# Removing the “flagship” tag, brace for impact.
|
|||
|
flagship, index = db_ships_by_tags.get_with_keys("flagship")[0]
|
|||
|
flagship = flagship.clone
|
|||
|
flagship.tags = [] of String
|
|||
|
db[index] = flagship
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# ship, index = db_ships_by_tags.update(tag: "flagship") do |ship, index|
|
|||
|
# ship.tags = [] of String
|
|||
|
# db[index] = ship
|
|||
|
# end
|
|||
|
|
|||
|
db_ships_by_tags.get("flagship").should eq([] of Ship)
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "gets items that have multiple tags" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
results = db_ships_by_tags.get(["flagship", "name ship"])
|
|||
|
results.should eq([Ship.yamato])
|
|||
|
|
|||
|
results = db_ships_by_tags.get(["name ship", "flagship"])
|
|||
|
results.should eq([Ship.yamato])
|
|||
|
|
|||
|
results = db_ships_by_tags.get(["flagship"])
|
|||
|
results.should eq([Ship.yamato])
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "atomic operations" do
|
|||
|
it "safe_get and safe_get?" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db_ships_by_name.safe_get ship.name do |results|
|
|||
|
results.should eq(ship)
|
|||
|
end
|
|||
|
|
|||
|
db_ships_by_name.safe_get? ship.name do |results|
|
|||
|
results.should eq(ship)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
describe "tools" do
|
|||
|
it "rebuilds indexes" do
|
|||
|
db = SPECDB::Cached(Ship).new
|
|||
|
|
|||
|
db_ships_by_name = db.new_index "name", &.name
|
|||
|
db_ships_by_class = db.new_partition "class", &.klass
|
|||
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db << ship
|
|||
|
end
|
|||
|
|
|||
|
db.reindex_everything!
|
|||
|
|
|||
|
Ship.all_ships.each do |ship|
|
|||
|
db_ships_by_name.get?(ship.name).should eq(ship)
|
|||
|
db_ships_by_class.get(ship.klass).should contain(ship)
|
|||
|
end
|
|||
|
|
|||
|
db.rm_storage_dir
|
|||
|
end
|
|||
|
|
|||
|
it "migrates properly" do
|
|||
|
old_db = SPECDB::Cached(PrimitiveShip).new "-migration-origin"
|
|||
|
|
|||
|
old_ships_by_name = old_db.new_index "name", &.name
|
|||
|
old_ships_by_class = old_db.new_partition "class", &.class_name
|
|||
|
|
|||
|
PrimitiveShip.all_ships.each do |ship|
|
|||
|
old_db << ship
|
|||
|
end
|
|||
|
|
|||
|
# At this point, the “old” DB is filled. Now we need to convert
|
|||
|
# to the new DB.
|
|||
|
|
|||
|
new_db = SPECDB::Cached(Ship).new "-migration-target"
|
|||
|
|
|||
|
new_ships_by_name = new_db.new_index "name", &.name
|
|||
|
new_ships_by_class = new_db.new_partition "class", &.klass
|
|||
|
new_ships_by_tags = new_db.new_tags "tags", &.tags
|
|||
|
|
|||
|
old_db.each_with_key do |ship, index|
|
|||
|
new_ship = Ship.new ship.name,
|
|||
|
klass: ship.class_name,
|
|||
|
id: ship.id,
|
|||
|
tags: Array(String).new.tap { |tags|
|
|||
|
tags << "name ship" if ship.name == ship.class_name
|
|||
|
}
|
|||
|
|
|||
|
new_db[index] = new_ship
|
|||
|
end
|
|||
|
|
|||
|
# At this point, the conversion is done, so… we’re making a few
|
|||
|
# arbitrary tests on the new data.
|
|||
|
|
|||
|
old_db.each_with_key do |old_ship, old_index|
|
|||
|
ship = new_db[old_index]
|
|||
|
|
|||
|
ship.id.should eq(old_ship.id)
|
|||
|
ship.klass.should eq(old_ship.class_name)
|
|||
|
|
|||
|
ship.tags.any?(&.==("name ship")).should be_true if ship.name == ship.klass
|
|||
|
end
|
|||
|
|
|||
|
old_db.rm_storage_dir
|
|||
|
new_db.rm_storage_dir
|
|||
|
end
|
|||
|
end
|
|||
|
end
|