Skip to content

Commit 29f9c06

Browse files
authored
Merge pull request #8 from e-e-e/namespaces
Add prefixes, and option for index config
2 parents ec0c2fc + f013dc8 commit 29f9c06

17 files changed

Lines changed: 810 additions & 399 deletions

index.js

Lines changed: 193 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,167 @@ const inherits = require('inherits')
55
const events = require('events')
66
const SparqlIterator = require('sparql-iterator')
77

8+
const constants = require('./lib/constants')
89
const utils = require('./lib/utils')
10+
const prefixes = require('./lib/prefixes')
911
const Variable = require('./lib/Variable')
1012
const HyperdbReadTransform = require('./lib/HyperdbReadTransform')
1113
const JoinStream = require('./lib/JoinStream')
1214
const planner = require('./lib/planner')
13-
const attachCreateReadStream = require('./lib/hyperdbModifier').attachCreateReadStream
15+
const pkg = require('./package.json')
1416

1517
const Transform = stream.Transform
1618
const PassThrough = stream.PassThrough
1719

18-
// temporarily augment hyperdb prototype to include createReadStream
19-
if (!hyperdb.createReadStream) {
20-
attachCreateReadStream(hyperdb)
21-
}
22-
2320
function Graph (storage, key, opts) {
2421
if (!(this instanceof Graph)) return new Graph(storage, key, opts)
2522
events.EventEmitter.call(this)
23+
24+
if (typeof key === 'string') key = Buffer.from(key, 'hex')
25+
26+
if (!Buffer.isBuffer(key) && !opts) {
27+
opts = key
28+
key = null
29+
}
30+
31+
if (!opts) opts = {}
2632
this.db = hyperdb(storage, key, opts)
33+
this._prefixes = Object.assign({}, opts.prefixes || constants.DEFAULT_PREFIXES)
34+
this._basename = opts.name || constants.DEFAULT_BASE
35+
this._prefixes._ = this._basename
36+
this._indexType = opts.index === 'tri' ? 'tri' : 'hex'
37+
this._indexes = opts.index === 'tri'
38+
? constants.HEXSTORE_INDEXES_REDUCED
39+
: constants.HEXSTORE_INDEXES
40+
this._indexKeys = Object.keys(this._indexes)
2741

2842
this.db.on('error', (e) => {
2943
this.emit('error', e)
3044
})
3145
this.db.on('ready', (e) => {
32-
this.emit('ready', e)
46+
if (utils.isNewDatabase(this.db)) {
47+
this._onNew((err) => {
48+
if (err) return this.emit('error', err)
49+
this.emit('ready', e)
50+
})
51+
} else {
52+
this._onInit((err) => {
53+
if (err) return this.emit('error', err)
54+
this.emit('ready', e)
55+
})
56+
}
3357
})
3458
}
3559

3660
inherits(Graph, events.EventEmitter)
3761

62+
Graph.prototype._onNew = function (cb) {
63+
this._version = pkg.version
64+
const metadata = [
65+
['@version', pkg.version],
66+
['@index', this._indexType],
67+
['@name', this._basename]
68+
]
69+
Object.keys(this._prefixes).forEach((key) => {
70+
if (key !== '_') {
71+
metadata.push([prefixes.toKey(key), this._prefixes[key]])
72+
}
73+
})
74+
utils.put(this.db, metadata, cb)
75+
}
76+
77+
Graph.prototype._onInit = function (cb) {
78+
// get and set graph version
79+
this._version = null
80+
this._basename = null
81+
this._indexType = null
82+
83+
let missing = 4
84+
let error = null
85+
// get and set version
86+
this.graphVersion((err, version) => {
87+
if (err) error = err
88+
this._version = version
89+
maybeDone()
90+
})
91+
// get and set graph name
92+
this.name((err, name) => {
93+
if (err) error = err
94+
this._basename = name || constants.DEFAULT_BASE
95+
// modify prefixes to ensure correct namespacing
96+
this._prefixes._ = this._basename
97+
maybeDone()
98+
})
99+
// get and set graph indexation
100+
this.indexType((err, index) => {
101+
if (err) error = err
102+
this._indexType = index || 'hex'
103+
this._indexes = index === 'tri'
104+
? constants.HEXSTORE_INDEXES_REDUCED
105+
: constants.HEXSTORE_INDEXES
106+
this._indexKeys = Object.keys(this._indexes)
107+
maybeDone()
108+
})
109+
// get and set prefixes
110+
this.prefixes((err, prefixes) => {
111+
if (err) error = err
112+
this._prefixes = Object.assign({ _: this._prefixes }, prefixes)
113+
maybeDone()
114+
})
115+
function maybeDone () {
116+
missing--
117+
if (!missing) {
118+
cb(error)
119+
}
120+
}
121+
}
122+
38123
Graph.prototype.v = (name) => new Variable(name)
39124

125+
function returnValueAsString (cb) {
126+
return (err, nodes) => {
127+
if (err) return cb(err)
128+
if (!nodes || nodes.length === 0) return cb(null, null)
129+
cb(null, nodes[0].value.toString())
130+
}
131+
}
132+
133+
Graph.prototype.graphVersion = function (cb) {
134+
if (this._version) return cb(null, this._version)
135+
this.db.get('@version', returnValueAsString(cb))
136+
}
137+
138+
Graph.prototype.name = function (cb) {
139+
if (this._basename) return cb(null, this._basename)
140+
this.db.get('@name', returnValueAsString(cb))
141+
}
142+
143+
Graph.prototype.indexType = function (cb) {
144+
if (this._indexType) return cb(null, this._indexType)
145+
this.db.get('@index', returnValueAsString(cb))
146+
}
147+
148+
Graph.prototype.prefixes = function (callback) {
149+
// should cache this somehow
150+
const prefixStream = this.db.createReadStream(constants.PREFIX_KEY)
151+
utils.collect(prefixStream, (err, data) => {
152+
if (err) return callback(err)
153+
var names = data.reduce((p, nodes) => {
154+
var data = prefixes.fromNodes(nodes)
155+
p[data.prefix] = data.uri
156+
return p
157+
}, {})
158+
callback(null, names)
159+
})
160+
}
161+
162+
Graph.prototype.addPrefix = function (prefix, uri, cb) {
163+
this.db.put(prefixes.toKey(prefix), uri, cb)
164+
}
165+
40166
Graph.prototype.getStream = function (triple, opts) {
41-
const stream = this.db.createReadStream(utils.createQuery(triple))
42-
return stream.pipe(new HyperdbReadTransform(this.db, opts))
167+
const stream = this.db.createReadStream(this._createQuery(triple, { encode: (!opts || opts.encode === undefined) ? true : opts.encode }))
168+
return stream.pipe(new HyperdbReadTransform(this.db, this._basename, opts))
43169
}
44170

45171
Graph.prototype.get = function (triple, opts, callback) {
@@ -52,21 +178,22 @@ function doAction (action) {
52178
if (!triples) return callback(new Error('Must pass triple'))
53179
let entries = (!triples.reduce) ? [triples] : triples
54180
entries = entries.reduce((prev, triple) => {
55-
return prev.concat(this.generateBatch(triple, action))
181+
return prev.concat(this._generateBatch(triple, action))
56182
}, [])
57183
this.db.batch(entries.reverse(), callback)
58184
}
59185
}
60186

61187
function doActionStream (action) {
62188
return function () {
189+
const self = this
63190
const transform = new Transform({
64191
objectMode: true,
65192
transform (triples, encoding, done) {
66193
if (!triples) return done()
67194
let entries = (!triples.reduce) ? [triples] : triples
68195
entries = entries.reduce((prev, triple) => {
69-
return prev.concat(utils.generateBatch(triple, action))
196+
return prev.concat(self._generateBatch(triple, action))
70197
}, [])
71198
this.push(entries.reverse())
72199
done()
@@ -95,8 +222,7 @@ Graph.prototype.searchStream = function (query, options) {
95222
} else if (!Array.isArray(query)) {
96223
query = [ query ]
97224
}
98-
const plannedQuery = planner(query)
99-
225+
const plannedQuery = planner(query, this._prefixes)
100226
var streams = plannedQuery.map((triple, i) => {
101227
const limit = (options && i === plannedQuery.length - 1) ? options.limit : undefined
102228
return new JoinStream({
@@ -131,10 +257,62 @@ Graph.prototype.query = function (query, callback) {
131257
utils.collect(this.queryStream(query), callback)
132258
}
133259

134-
Graph.prototype.generateBatch = utils.generateBatch
135-
136260
Graph.prototype.close = function (callback) {
137261
callback()
138262
}
139263

264+
/* PRIVATE FUNCTIONS */
265+
266+
Graph.prototype._generateBatch = function (triple, action) {
267+
if (!action) action = 'put'
268+
var data = null
269+
if (action === 'put') {
270+
data = JSON.stringify(utils.extraDataMask(triple))
271+
}
272+
return this._encodeKeys(triple).map(key => ({
273+
type: 'put', // no delete in hyperdb so just putting nulls
274+
key: key,
275+
value: data
276+
}))
277+
}
278+
279+
Graph.prototype._encodeKeys = function (triple) {
280+
const encodedTriple = utils.encodeTriple(triple, this._prefixes)
281+
return this._indexKeys.map(key => utils.encodeKey(key, encodedTriple))
282+
}
283+
284+
Graph.prototype._createQuery = function (pattern, options) {
285+
var types = utils.typesFromPattern(pattern)
286+
var preferedIndex = options && options.index
287+
var index = this._findIndex(types, preferedIndex)
288+
const encodedTriple = utils.encodeTriple(pattern, options.encode ? this._prefixes : { _: this._basename })
289+
var key = utils.encodeKey(index, encodedTriple)
290+
return key
291+
}
292+
293+
Graph.prototype._possibleIndexes = function (types) {
294+
var result = this._indexKeys.filter((key) => {
295+
var matches = 0
296+
return this._indexes[key].every(function (e, i) {
297+
if (types.indexOf(e) >= 0) {
298+
matches++
299+
return true
300+
}
301+
if (matches === types.length) {
302+
return true
303+
}
304+
})
305+
})
306+
result.sort()
307+
return result
308+
}
309+
310+
Graph.prototype._findIndex = function (types, preferedIndex) {
311+
var result = this._possibleIndexes(types)
312+
if (preferedIndex && result.some(r => r === preferedIndex)) {
313+
return preferedIndex
314+
}
315+
return result[0]
316+
}
317+
140318
module.exports = Graph

lib/HyperdbReadTransform.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ const Transform = require('readable-stream').Transform
22
const inherits = require('inherits')
33
const utils = require('./utils')
44

5-
function HyperdbReadTransform (db, options) {
5+
function HyperdbReadTransform (db, basename, options) {
66
if (!(this instanceof HyperdbReadTransform)) {
7-
return new HyperdbReadTransform(db, options)
7+
return new HyperdbReadTransform(db, basename, options)
88
}
99
var opts = options || {}
1010
this.db = db
11-
this._finished = false
11+
this._prefixes = { _: basename }
1212
this._count = 0
1313
this._filter = opts.filter
1414
this._offset = opts.offset || 0
@@ -24,23 +24,24 @@ function HyperdbReadTransform (db, options) {
2424
inherits(HyperdbReadTransform, Transform)
2525

2626
HyperdbReadTransform.prototype._transform = function transform (nodes, encoding, done) {
27-
if (this._finished) return done()
28-
if (this._limit && this._count >= this._limit) {
29-
this.push(null)
30-
this._sources.forEach(source => source.destroy())
31-
this._finished = true
32-
return
33-
}
3427
var value = nodes[0].value && JSON.parse(nodes[0].value.toString())
3528
if (value === null) return done()
36-
value = Object.assign(value, utils.decodeKey(nodes[0].key))
29+
value = Object.assign(value, utils.decodeKey(nodes[0].key, this._prefixes))
3730
if (!this._filter || this._filter(value)) {
3831
if (this._count >= this._offset) {
3932
this.push(value)
4033
}
4134
this._count++
35+
if (this._limit && this._count >= this._limit) {
36+
this.end()
37+
}
4238
}
4339
done()
4440
}
4541

42+
HyperdbReadTransform.prototype._flush = function (done) {
43+
this._sources.forEach(source => source.destroy())
44+
done()
45+
}
46+
4647
module.exports = HyperdbReadTransform

lib/JoinStream.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ function JoinStream (options) {
1616
this.matcher = matcher(options.triple)
1717
this.mask = queryMask(options.triple)
1818
this.maskUpdater = maskUpdater(options.triple)
19-
this.limit = options && options.limit
19+
this.limit = options.limit
2020
this._limitCounter = 0
2121
this.db = options.db
2222
this._ended = false
23-
this.filter = options && options.filter
24-
this.offset = options && options.offset
23+
this.filter = options.filter
24+
this.offset = options.offset
2525

2626
this.once('pipe', (source) => {
2727
source.on('error', (err) => {
@@ -49,7 +49,11 @@ function JoinStream (options) {
4949
}
5050
}
5151

52-
this._options = { filter: this.filter, offset: this.offset }
52+
this._options = {
53+
filter: this.filter,
54+
offset: this.offset,
55+
encode: options.encode ? !!options.encode : false
56+
}
5357
}
5458

5559
inherits(JoinStream, Transform)

lib/constants.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const HEXSTORE_INDEXES = {
2+
spo: ['subject', 'predicate', 'object'],
3+
pos: ['predicate', 'object', 'subject'],
4+
osp: ['object', 'subject', 'predicate'],
5+
sop: ['subject', 'object', 'predicate'], // [optional]
6+
pso: ['predicate', 'subject', 'object'], // [optional]
7+
ops: ['object', 'predicate', 'subject'] // [optional]
8+
}
9+
const HEXSTORE_INDEXES_REDUCED = {
10+
spo: HEXSTORE_INDEXES.spo,
11+
pos: HEXSTORE_INDEXES.pos,
12+
osp: HEXSTORE_INDEXES.osp
13+
}
14+
const PREFIX_KEY = '@prefix/'
15+
const DEFAULT_BASE = 'hg://'
16+
const DEFAULT_PREFIXES = {
17+
foaf: 'http://xmlns.com/foaf/0.1/'
18+
}
19+
20+
module.exports = {
21+
PREFIX_KEY,
22+
DEFAULT_BASE,
23+
DEFAULT_PREFIXES,
24+
HEXSTORE_INDEXES,
25+
HEXSTORE_INDEXES_REDUCED
26+
}

0 commit comments

Comments
 (0)