@dstanesc/o-o-o-o-o-o-o
Version:
O-O-O-O-O-O-O is a collection of content addressed persistent data structures
827 lines (729 loc) • 24.2 kB
text/typescript
import { protoGremlinFactory, ProtoGremlin } from '../api/proto-gremlin'
import * as assert from 'assert'
import bent from 'bent'
import { Prop, VertexRef } from '../types'
import { compute_chunks } from '@dstanesc/wasm-chunking-fastcdc-node'
import { chunkerFactory } from '../chunking'
import {
BlockStore,
MemoryBlockStore,
memoryBlockStoreFactory,
} from '../block-store'
import {
LinkCodec,
linkCodecFactory,
ValueCodec,
valueCodecFactory,
} from '../codecs'
import { indexStoreFactory } from '../index-store-factory'
import { eq } from '../ops'
import { VersionStore, versionStoreFactory } from '../version-store'
import { navigateVertices, PathElemType, RequestBuilder } from '../navigate'
import { Graph } from '../graph'
const getStream = bent('https://raw.githubusercontent.com')
enum ObjectTypes {
ROOT = 1,
BOOK = 2,
CHAPTER = 3,
VERSE = 4,
}
enum RlshpTypes {
book = 1,
chapter = 2,
verse = 3,
}
enum PropTypes {
ANY = 1,
}
enum KeyTypes {
ID = 1,
NAME = 2,
TEXT = 3,
}
enum IndexTypes {
BOOK_ID = 2,
CHAPTER_ID = 3,
VERSE_ID = 4,
}
describe('e2e ', function () {
test('full bible, 7MB json, no index, load and navigate', async () => {
const stream = await getStream(
'/dstanesc/bibleapi-bibles-json/legacy/kjv.json'
)
const str = (await stream.text()).trim()
const lines = str.split(/\r?\n/g)
const { chunk } = chunkerFactory(1024 * 48, compute_chunks)
const linkCodec: LinkCodec = linkCodecFactory()
const valueCodec: ValueCodec = valueCodecFactory()
const blockStore: MemoryBlockStore = memoryBlockStoreFactory()
const versionStore: VersionStore = await versionStoreFactory({
chunk,
linkCodec,
valueCodec,
blockStore,
})
const g: ProtoGremlin = protoGremlinFactory({
chunk,
linkCodec,
valueCodec,
blockStore,
versionStore,
}).g()
const tx = await g.tx()
const bible = await tx.addV(ObjectTypes.ROOT).next()
let book: any, book_id: string
let chapter: any, chapter_id: number
let verse: any, verse_id: number
for (const line of lines) {
const entry = JSON.parse(line)
if (book === undefined || book_id !== entry.book_id) {
book = await tx
.addV(ObjectTypes.BOOK)
.property(KeyTypes.ID, entry.book_id, PropTypes.ANY)
.property(KeyTypes.NAME, entry.book_name, PropTypes.ANY)
.next()
book_id = entry.book_id
await tx.addE(RlshpTypes.book).from(bible).to(book).next()
}
if (chapter === undefined || chapter_id !== entry.chapter) {
chapter = await tx
.addV(ObjectTypes.CHAPTER)
.property(KeyTypes.ID, entry.chapter, PropTypes.ANY)
.next()
chapter_id = entry.chapter
await tx.addE(RlshpTypes.chapter).from(book).to(chapter).next()
}
if (verse === undefined || verse_id !== entry.verse) {
verse = await tx
.addV(ObjectTypes.VERSE)
.property(KeyTypes.ID, entry.verse, PropTypes.ANY)
.property(KeyTypes.TEXT, entry.text, PropTypes.ANY)
.next()
verse_id = entry.verse
await tx.addE(RlshpTypes.verse).from(chapter).to(verse).next()
}
}
const { root, index, blocks } = await tx.commit({})
const g2: ProtoGremlin = protoGremlinFactory({
chunk,
linkCodec,
valueCodec,
blockStore,
versionStore,
}).g()
blockStore.resetReads()
const first = await queryVerse(g2, bible.offset, 'Gen', 1, 1)
const readsFirst = blockStore.countReads()
console.log(`Quick scan reads ${readsFirst}`)
assert.equal(
first,
'In the beginning God created the heaven and the earth.'
)
blockStore.resetReads()
const last = await queryVerse(g2, bible.offset, 'Rev', 22, 21)
const readsLast = blockStore.countReads()
console.log(`Full scan reads ${readsLast}`)
assert.equal(
last,
'The grace of our Lord Jesus Christ be with you all. Amen.'
)
console.log(`BlockStore total size = ${blockStore.size()}`)
console.log(
`Root ${root.toString()} storeRoot=${versionStore
.versionStoreRoot()
.toString()}`
)
/**
* Validate push
*/
const blockStore2: MemoryBlockStore = memoryBlockStoreFactory()
await blockStore.push(blockStore2)
const versionStore2: VersionStore = await versionStoreFactory({
readOnly: true,
storeRoot: versionStore.versionStoreRoot(),
versionRoot: undefined, // HEAD, can be omitted
chunk,
linkCodec,
valueCodec,
blockStore: blockStore2,
})
const g3: ProtoGremlin = protoGremlinFactory({
chunk,
linkCodec,
valueCodec,
blockStore: blockStore2,
versionStore: versionStore2,
}).g()
const first2 = await queryVerse(g3, bible.offset, 'Gen', 1, 1)
assert.equal(
first2,
'In the beginning God created the heaven and the earth.'
)
const last2 = await queryVerse(g3, bible.offset, 'Rev', 22, 21)
assert.equal(
last2,
'The grace of our Lord Jesus Christ be with you all. Amen.'
)
assert.strictEqual(blockStore.size(), blockStore2.size())
const books = await queryBooks(g3, bible.offset)
})
test('full bible, 7MB json, no index, query first level', async () => {
const stream = await getStream(
'/dstanesc/bibleapi-bibles-json/legacy/kjv.json'
)
const str = (await stream.text()).trim()
const lines = str.split(/\r?\n/g)
const { chunk } = chunkerFactory(1024 * 48, compute_chunks)
const linkCodec: LinkCodec = linkCodecFactory()
const valueCodec: ValueCodec = valueCodecFactory()
const blockStore: MemoryBlockStore = memoryBlockStoreFactory()
const versionStore: VersionStore = await versionStoreFactory({
chunk,
linkCodec,
valueCodec,
blockStore,
})
const g: ProtoGremlin = protoGremlinFactory({
chunk,
linkCodec,
valueCodec,
blockStore,
versionStore,
}).g()
const tx = await g.tx()
const bible = await tx.addV(ObjectTypes.ROOT).next()
let book: any, book_id: string
let chapter: any, chapter_id: number
let verse: any, verse_id: number
for (const line of lines) {
const entry = JSON.parse(line)
if (book === undefined || book_id !== entry.book_id) {
book = await tx
.addV(ObjectTypes.BOOK)
.property(KeyTypes.ID, entry.book_id, PropTypes.ANY)
.property(KeyTypes.NAME, entry.book_name, PropTypes.ANY)
.next()
book_id = entry.book_id
await tx.addE(RlshpTypes.book).from(bible).to(book).next()
}
if (chapter === undefined || chapter_id !== entry.chapter) {
chapter = await tx
.addV(ObjectTypes.CHAPTER)
.property(KeyTypes.ID, entry.chapter, PropTypes.ANY)
.next()
chapter_id = entry.chapter
await tx.addE(RlshpTypes.chapter).from(book).to(chapter).next()
}
if (verse === undefined || verse_id !== entry.verse) {
verse = await tx
.addV(ObjectTypes.VERSE)
.property(KeyTypes.ID, entry.verse, PropTypes.ANY)
.property(KeyTypes.TEXT, entry.text, PropTypes.ANY)
.next()
verse_id = entry.verse
await tx.addE(RlshpTypes.verse).from(chapter).to(verse).next()
}
}
const { root, index, blocks } = await tx.commit({})
const books = await queryBooks(g, bible.offset)
const expected = [
'Gen',
'Exod',
'Lev',
'Num',
'Deut',
'Josh',
'Judg',
'Ruth',
'1Sam',
'2Sam',
'1Kgs',
'2Kgs',
'1Chr',
'2Chr',
'Ezra',
'Neh',
'Esth',
'Job',
'Ps',
'Prov',
'Eccl',
'Song',
'Isa',
'Jer',
'Lam',
'Ezek',
'Dan',
'Hos',
'Joel',
'Amos',
'Obad',
'Jona',
'Mic',
'Nah',
'Hab',
'Zeph',
'Hag',
'Zech',
'Mal',
'Matt',
'Mark',
'Luke',
'John',
'Acts',
'Rom',
'1Cor',
'2Cor',
'Gal',
'Eph',
'Phil',
'Col',
'1Thess',
'2Thess',
'1Tim',
'2Tim',
'Titus',
'Phlm',
'Heb',
'Jas',
'1Pet',
'2Pet',
'1John',
'2John',
'3John',
'Jude',
'Rev',
]
assert.deepStrictEqual(books, expected)
})
test('full bible, 7MB json, KeyTypes.ID indexed, load and navigate', async () => {
const stream = await getStream(
'/dstanesc/bibleapi-bibles-json/legacy/kjv.json'
)
const str = (await stream.text()).trim()
const lines = str.split(/\r?\n/g)
const { chunk } = chunkerFactory(1024 * 48, compute_chunks)
const linkCodec: LinkCodec = linkCodecFactory()
const valueCodec: ValueCodec = valueCodecFactory()
const blockStore: MemoryBlockStore = memoryBlockStoreFactory()
const versionStore: VersionStore = await versionStoreFactory({
chunk,
linkCodec,
valueCodec,
blockStore,
})
const indexStore = indexStoreFactory(blockStore)
const g: ProtoGremlin = protoGremlinFactory({
chunk,
linkCodec,
valueCodec,
blockStore,
versionStore,
indexStore,
}).g()
const tx = await g.tx()
const bible = await tx.addV(ObjectTypes.ROOT).next()
let book: any, book_id: string
let chapter: any, chapter_id: number
let verse: any, verse_id: number
const books = []
const chapters = []
const verses = []
for (const line of lines) {
const entry = JSON.parse(line)
if (book === undefined || book_id !== entry.book_id) {
book = await tx
.addV(ObjectTypes.BOOK)
.property(KeyTypes.ID, entry.book_id, PropTypes.ANY)
.property(KeyTypes.NAME, entry.book_name, PropTypes.ANY)
.next()
book_id = entry.book_id
await tx.addE(RlshpTypes.book).from(bible).to(book).next()
books.push(book)
}
if (chapter === undefined || chapter_id !== entry.chapter) {
chapter = await tx
.addV(ObjectTypes.CHAPTER)
.property(KeyTypes.ID, entry.chapter, PropTypes.ANY)
.next()
chapter_id = entry.chapter
await tx.addE(RlshpTypes.chapter).from(book).to(chapter).next()
chapters.push(chapter)
}
if (verse === undefined || verse_id !== entry.verse) {
verse = await tx
.addV(ObjectTypes.VERSE)
.property(KeyTypes.ID, entry.verse, PropTypes.ANY)
.property(KeyTypes.TEXT, entry.text, PropTypes.ANY)
.next()
verse_id = entry.verse
await tx.addE(RlshpTypes.verse).from(chapter).to(verse).next()
verses.push(verse)
}
}
for (const book of books) {
await book.uniqueIndex(KeyTypes.ID)
}
for (const chapter of chapters) {
await chapter.uniqueIndex(KeyTypes.ID)
}
for (const verse of verses) {
await verse.uniqueIndex(KeyTypes.ID)
}
const { root, index, blocks } = await tx.commit({})
const g2: ProtoGremlin = protoGremlinFactory({
chunk,
linkCodec,
valueCodec,
blockStore,
versionStore,
indexStore,
}).g()
blockStore.resetReads()
const first = await queryVerse(g2, bible.offset, 'Gen', 1, 1)
const readsFirst = blockStore.countReads()
console.log(`Indexed reads first ${readsFirst}`)
assert.equal(
first,
'In the beginning God created the heaven and the earth.'
)
blockStore.resetReads()
const last = await queryVerse(g2, bible.offset, 'Rev', 22, 21)
const readsLast = blockStore.countReads()
console.log(`Indexed reads last ${readsLast}`)
assert.equal(
last,
'The grace of our Lord Jesus Christ be with you all. Amen.'
)
console.log(`BlockStore total size = ${blockStore.size()}`)
console.log(
`Root ${root.toString()} storeRoot=${versionStore
.versionStoreRoot()
.toString()}`
)
})
test('full bible, 7MB json, no index, template based retrieval', async () => {
const stream = await getStream(
'/dstanesc/bibleapi-bibles-json/legacy/kjv.json'
)
const str = (await stream.text()).trim()
const lines = str.split(/\r?\n/g)
const { chunk } = chunkerFactory(1024 * 48, compute_chunks)
const linkCodec: LinkCodec = linkCodecFactory()
const valueCodec: ValueCodec = valueCodecFactory()
const blockStore: MemoryBlockStore = memoryBlockStoreFactory()
const versionStore: VersionStore = await versionStoreFactory({
chunk,
linkCodec,
valueCodec,
blockStore,
})
const g: ProtoGremlin = protoGremlinFactory({
chunk,
linkCodec,
valueCodec,
blockStore,
versionStore,
}).g()
const tx = await g.tx()
const bible = await tx.addV(ObjectTypes.ROOT).next()
let book: any, book_id: string
let chapter: any, chapter_id: number
let verse: any, verse_id: number
for (const line of lines) {
const entry = JSON.parse(line)
if (book === undefined || book_id !== entry.book_id) {
book = await tx
.addV(ObjectTypes.BOOK)
.property(KeyTypes.ID, entry.book_id, PropTypes.ANY)
.property(KeyTypes.NAME, entry.book_name, PropTypes.ANY)
.next()
book_id = entry.book_id
await tx.addE(RlshpTypes.book).from(bible).to(book).next()
}
if (chapter === undefined || chapter_id !== entry.chapter) {
chapter = await tx
.addV(ObjectTypes.CHAPTER)
.property(KeyTypes.ID, entry.chapter, PropTypes.ANY)
.next()
chapter_id = entry.chapter
await tx.addE(RlshpTypes.chapter).from(book).to(chapter).next()
}
if (verse === undefined || verse_id !== entry.verse) {
verse = await tx
.addV(ObjectTypes.VERSE)
.property(KeyTypes.ID, entry.verse, PropTypes.ANY)
.property(KeyTypes.TEXT, entry.text, PropTypes.ANY)
.next()
verse_id = entry.verse
await tx.addE(RlshpTypes.verse).from(chapter).to(verse).next()
}
}
const { root, index, blocks } = await tx.commit({})
const books = await queryBooksTemplateGremlin(
g,
bible.offset,
BOOKS_TEMPLATE_1
)
assert.strictEqual(books.length, 66)
assert.strictEqual(books[0].id, 'Gen')
assert.strictEqual(books[0].name, 'Genesis')
assert.deepStrictEqual(books[0].chapters, [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
{ id: 5 },
{ id: 6 },
{ id: 7 },
{ id: 8 },
{ id: 9 },
{ id: 10 },
{ id: 11 },
{ id: 12 },
{ id: 13 },
{ id: 14 },
{ id: 15 },
{ id: 16 },
{ id: 17 },
{ id: 18 },
{ id: 19 },
{ id: 20 },
{ id: 21 },
{ id: 22 },
{ id: 23 },
{ id: 24 },
{ id: 25 },
{ id: 26 },
{ id: 27 },
{ id: 28 },
{ id: 29 },
{ id: 30 },
{ id: 31 },
{ id: 32 },
{ id: 33 },
{ id: 34 },
{ id: 35 },
{ id: 36 },
{ id: 37 },
{ id: 38 },
{ id: 39 },
{ id: 40 },
{ id: 41 },
{ id: 42 },
{ id: 43 },
{ id: 44 },
{ id: 45 },
{ id: 46 },
{ id: 47 },
{ id: 48 },
{ id: 49 },
{ id: 50 },
])
assert.strictEqual(books[1].id, 'Exod')
assert.strictEqual(books[1].name, 'Exodus')
assert.deepStrictEqual(books[1].chapters, [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
{ id: 5 },
{ id: 6 },
{ id: 7 },
{ id: 8 },
{ id: 9 },
{ id: 10 },
{ id: 11 },
{ id: 12 },
{ id: 13 },
{ id: 14 },
{ id: 15 },
{ id: 16 },
{ id: 17 },
{ id: 18 },
{ id: 19 },
{ id: 20 },
{ id: 21 },
{ id: 22 },
{ id: 23 },
{ id: 24 },
{ id: 25 },
{ id: 26 },
{ id: 27 },
{ id: 28 },
{ id: 29 },
{ id: 30 },
{ id: 31 },
{ id: 32 },
{ id: 33 },
{ id: 34 },
{ id: 35 },
{ id: 36 },
{ id: 37 },
{ id: 38 },
{ id: 39 },
{ id: 40 },
])
const books2 = await queryBooksTemplateGremlin(
g,
bible.offset,
BOOKS_TEMPLATE_2
)
assert.strictEqual(books2.length, 66)
assert.strictEqual(books2[0].id, 'Gen')
assert.strictEqual(books2[0].name, 'Genesis')
assert.deepStrictEqual(
books2[0].chapters,
[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
]
)
assert.deepStrictEqual(books2[65], {
id: 'Rev',
name: 'Revelation',
chapters: [
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22,
],
})
const books3 = await queryBooksTemplateNative(
g.graph,
bible.offset,
BOOKS_TEMPLATE_2
)
assert.strictEqual(books3.length, 66)
assert.deepStrictEqual(books3[0], {
id: 'Gen',
name: 'Genesis',
chapters: [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
],
})
assert.deepStrictEqual(books3[65], {
id: 'Rev',
name: 'Revelation',
chapters: [
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22,
],
})
})
})
async function queryVerse(
g: ProtoGremlin,
rootOffset: VertexRef,
book: string,
chapter: number,
verse: number
): Promise<string> {
const vr = []
const startTime = new Date().getTime()
for await (const result of g
.V([rootOffset])
.out(RlshpTypes.book)
.has(ObjectTypes.BOOK, { keyType: KeyTypes.ID, operation: eq(book) })
.out(RlshpTypes.chapter)
.has(ObjectTypes.CHAPTER, {
keyType: KeyTypes.ID,
operation: eq(chapter),
})
.out(RlshpTypes.verse)
.has(ObjectTypes.VERSE, { keyType: KeyTypes.ID, operation: eq(verse) })
.values(KeyTypes.TEXT)
.maxResults(1)
.exec()) {
vr.push(result)
}
const endTime = new Date().getTime()
console.log(`Query Time ${endTime - startTime} ms`)
return vr[0].value
}
async function queryBooks(g: ProtoGremlin, rootOffset: VertexRef) {
const books = []
for await (const result of g
.V([rootOffset])
.out(RlshpTypes.book)
.hasType(ObjectTypes.BOOK)
.values(KeyTypes.ID)
.exec()) {
books.push((result as Prop).value)
}
return books
}
const BOOKS_TEMPLATE_1 = {
id: {
$elemType: PathElemType.EXTRACT,
$type: KeyTypes.ID,
},
name: {
$elemType: PathElemType.EXTRACT,
$type: KeyTypes.NAME,
},
chapters: {
$elemType: PathElemType.EDGE,
$type: RlshpTypes.chapter,
id: {
$elemType: PathElemType.EXTRACT,
$type: KeyTypes.ID,
},
},
}
const BOOKS_TEMPLATE_2 = {
id: {
$elemType: PathElemType.EXTRACT,
$type: KeyTypes.ID,
},
name: {
$elemType: PathElemType.EXTRACT,
$type: KeyTypes.NAME,
},
chapters: {
$elemType: PathElemType.EDGE,
$type: RlshpTypes.chapter,
$value: {
$elemType: PathElemType.EXTRACT,
$type: KeyTypes.ID,
},
},
}
async function queryBooksTemplateGremlin(
g: ProtoGremlin,
rootOffset: VertexRef,
template: any
): Promise<any[]> {
const vr = []
const startTime = new Date().getTime()
for await (const result of g
.V([rootOffset])
.out(RlshpTypes.book)
.hasType(ObjectTypes.BOOK)
.template(template)
.maxResults(100)
.exec()) {
vr.push(result)
}
const endTime = new Date().getTime()
return vr
}
const queryBooksTemplateNative = async (
graph: Graph,
rootOffset: VertexRef,
template: any
): Promise<any[]> => {
const request = new RequestBuilder()
.add(PathElemType.VERTEX)
.add(PathElemType.EDGE)
.add(PathElemType.VERTEX)
.template(template)
.maxResults(100)
.get()
const vr: any[] = []
for await (const result of navigateVertices(graph, [rootOffset], request)) {
vr.push(result)
}
return vr
}