testing :3

This commit is contained in:
Rowan 2024-11-15 00:36:19 -06:00
parent 523a64869a
commit 6dc2c1f6db
5 changed files with 110 additions and 60 deletions

View file

@ -1,7 +1,7 @@
import { identifier, literal, Symbol, ws } from './common.js' import { identifier, literal, Symbol, ws } from './common.js'
import { Node, Edge, KeyValuePair, Label, Name } from './types.js' import { Node, Edge, KeyValuePair, Label, Name, Direction, DirectedEdge } from './types.js'
import { curry, is } from '../fn.js' import { curry, is } from '../fn.js'
import { many, maybe, map, seq, skip, between, noCaseString, separated, list } from '../parser.js' import { many, maybe, map, seq, skip, between, noCaseString, separated, list, any } from '../parser.js'
const { Bracket, Colon, Comma, Hyphen } = Symbol const { Bracket, Colon, Comma, Hyphen } = Symbol
@ -45,8 +45,17 @@ export const edge = map(
bracketed(seq(id, maybe(properties)), Bracket.Square) bracketed(seq(id, maybe(properties)), Bracket.Square)
) )
const rightArrow = seq(Hyphen, Bracket.Angle.Right) const arrowRight = map(() => Direction.Right, seq(Hyphen, Bracket.Angle.Right))
const relationship = seq(skip(Hyphen), edge, skip(rightArrow), node) const arrowLeft = map(() => Direction.Left, seq(Bracket.Angle.Left, Hyphen))
const relationshipRight = map(([edge, direction]) =>
DirectedEdge.fromEdge(edge, direction),
seq(skip(Hyphen), edge, arrowRight)
)
const relationshipLeft = map(([direction, edge]) =>
DirectedEdge.fromEdge(edge, direction),
seq(arrowLeft, edge, skip(Hyphen))
)
const relationship = seq(any(relationshipRight, relationshipLeft), node)
const keyword = noCaseString('match') const keyword = noCaseString('match')
const params = seq(node, many(relationship)) const params = seq(node, many(relationship))

View file

@ -30,12 +30,28 @@ export class Node extends GraphObject {
} }
} }
export const Direction = Object.freeze({
Left: 0,
Right: 1
})
export class Edge extends GraphObject { export class Edge extends GraphObject {
constructor(name, label, properties) { constructor(name, label, properties) {
super(name, label, properties) super(name, label, properties)
} }
} }
export class DirectedEdge extends Edge {
constructor(name, label, direction, properties) {
super(name, label, properties)
this.direction = direction
}
static fromEdge(edge, direction) {
return new DirectedEdge(edge.name, edge.label, direction, edge.properties)
}
}
export class ReturnValues extends Array { export class ReturnValues extends Array {
constructor(...args) { constructor(...args) {
super(...args) super(...args)

View file

@ -1,59 +1,83 @@
import { describe, it } from 'node:test' import { describe, it } from 'node:test'
import assert from 'node:assert' import assert from '../assert.js'
import { parse } from '../../src/parser.js'
import * as Common from '../../src/query/common.js'
import { Alias, Identifier, Literal, ObjectPath } from '../../src/query/types.js' import { Alias, Identifier, Literal, ObjectPath } from '../../src/query/types.js'
import { baseValue, literal, value } from '../../src/query/common.js'
const v = r => r.unwrap()[0][0]
describe('common parser library', () => { describe('common parser library', () => {
const literals = [
['true', true],
['false', false],
['5', 5],
['+16', 16],
['-710', -710],
['2.1', 2.1],
['+0.001', 0.001],
['-22.69', -22.69],
['"hellion"', 'hellion'],
]
it('literals should match literal types', () => { it('literals should match literal types', () => {
const tBool = parse(Common.literal, 'true') literals.forEach(([input, expected]) => {
const fBool = parse(Common.literal, 'false') assert.parseOk(literal, input, ([actual]) => {
const uint = parse(Common.literal, '5') assert.deepEqual(actual, new Literal(expected))
const posInt = parse(Common.literal, '+16') })
const negInt = parse(Common.literal, '-710') })
const ufloat = parse(Common.literal, '2.1')
const posFloat = parse(Common.literal, '+0.1')
const negFloat = parse(Common.literal, '-12.01')
const string = parse(Common.literal, '"akjsdfuaio"')
assert.deepEqual(v(tBool), new Literal(true))
assert.deepEqual(v(fBool), new Literal(false))
assert.deepEqual(v(uint), new Literal(5))
assert.deepEqual(v(posInt), new Literal(16))
assert.deepEqual(v(negInt), new Literal(-710))
assert.deepEqual(v(ufloat), new Literal(2.1))
assert.deepEqual(v(posFloat), new Literal(0.1))
assert.deepEqual(v(negFloat), new Literal(-12.01))
assert.deepEqual(v(string), new Literal('akjsdfuaio'))
}) })
it('value should match literals', () => { it('baseValue should match literals', () => {
const bool = parse(Common.baseValue, 'false') literals.forEach(([input, expected]) => {
const uint = parse(Common.baseValue, '11') assert.parseOk(baseValue, input, ([actual]) => {
const float = parse(Common.baseValue, '0.15') assert.deepEqual(actual, new Literal(expected))
const str = parse(Common.baseValue, '"abc"') })
})
assert.deepEqual(v(bool), new Literal(false))
assert.deepEqual(v(uint), new Literal(11))
assert.deepEqual(v(float), new Literal(0.15))
assert.deepEqual(v(str), new Literal('abc'))
}) })
it('value should match variables', () => { it('baseValue should parse identifiers', () => {
const identifier = parse(Common.baseValue, 'test') assert.parseOk(baseValue, 'red', ([actual]) => {
const accessor = parse(Common.baseValue, 'test.value') assert.deepEqual(actual, new Identifier('red'))
assert.deepEqual(v(identifier), new Identifier('test')) })
assert.deepEqual(v(accessor), new ObjectPath(new Identifier('test'), new Identifier('value')))
assert.parseOk(baseValue, 'ginger.snaps', ([actual]) => {
assert.deepEqual(actual, new ObjectPath(new Identifier('ginger'), new Identifier('snaps')))
})
}) })
it('aliases should work i hope', () => { it('value should parse literals', () => {
const noAlias = parse(Common.value, 'test') literals.forEach(([input, expected]) => {
const aliased1 = parse(Common.value, 'crybaby AS cb') assert.parseOk(value, input, ([actual]) => {
const aliased2 = parse(Common.value, 'property.name AS name') assert.deepEqual(actual, new Literal(expected))
assert.deepEqual(v(noAlias), new Identifier('test')) })
assert.deepEqual(v(aliased1), new Alias(new Identifier('crybaby'), new Identifier('cb'))) })
assert.deepEqual(v(aliased2), new Alias(new ObjectPath(new Identifier('property'), new Identifier('name')), new Identifier('name'))) })
it('value should parse identifiers', () => {
assert.parseOk(baseValue, 'violet', ([actual]) => {
assert.deepEqual(actual, new Identifier('violet'))
})
assert.parseOk(baseValue, 'monster.girl', ([actual]) => {
assert.deepEqual(actual, new ObjectPath(new Identifier('monster'), new Identifier('girl')))
})
})
it('value should parse aliases', () => {
assert.parseOk(value, '19589 AS sybil', ([actual]) => {
assert.deepEqual(actual, new Alias(new Literal(19589), new Identifier('sybil')))
})
assert.parseOk(value, '3.14159 AS PI', ([actual]) => {
assert.deepEqual(actual, new Alias(new Literal(3.14159), new Identifier('PI')))
})
assert.parseOk(value, 'crybaby AS cb', ([actual]) => {
assert.deepEqual(actual, new Alias(new Identifier('crybaby'), new Identifier('cb')))
})
assert.parseOk(value, 'rowan.containment.isBreached AS rawrnEscaped', ([actual]) => {
const obj = new ObjectPath(new Identifier('rowan'), new Identifier('containment'), new Identifier('isBreached'))
const alias = new Alias(obj, new Identifier('rawrnEscaped'))
assert.deepEqual(actual, alias)
})
}) })
}) })

View file

@ -1,7 +1,7 @@
import { describe, it } from 'node:test' import { describe, it } from 'node:test'
import assert from '../assert.js' import assert from '../assert.js'
import { node, edge, statement } from '../../src/query/match.js' import { node, edge, statement } from '../../src/query/match.js'
import { makeEdge, makeNode } from '../utils.js' import { makeDirectedEdge, makeEdge, makeNode } from '../utils.js'
describe('node', () => { describe('node', () => {
it('should match a node with a name, label, and properties', () => { it('should match a node with a name, label, and properties', () => {
@ -38,12 +38,12 @@ describe('node', () => {
describe('edge', () => { describe('edge', () => {
it('should match a relationship with a name, label, properties', () => { it('should match a relationship with a name, label, properties', () => {
assert.parseOk(edge, '[name:Label]', ([actual]) => { assert.parseOk(edge, '[name:Label]', ([actual]) => {
const expected = makeEdge('name', 'Label', []) const expected = makeEdge('name', 'Label')
assert.deepEqual(actual, expected) assert.deepEqual(actual, expected)
}) })
assert.parseOk(edge, '[:Label]', ([actual]) => { assert.parseOk(edge, '[:Label]', ([actual]) => {
const expected = makeEdge(undefined, 'Label', []) const expected = makeEdge(undefined, 'Label')
assert.deepEqual(actual, expected) assert.deepEqual(actual, expected)
}) })
@ -88,7 +88,7 @@ describe('MATCH keyword', () => {
assert.parseOk(statement, 'MATCH (:Node)-[:Edge]->(:Node)', actual => { assert.parseOk(statement, 'MATCH (:Node)-[:Edge]->(:Node)', actual => {
const expected = [ const expected = [
makeNode(undefined, 'Node'), makeNode(undefined, 'Node'),
makeEdge(undefined, 'Edge'), makeDirectedEdge(undefined, 'Edge', 1),
makeNode(undefined, 'Node'), makeNode(undefined, 'Node'),
] ]
@ -98,7 +98,7 @@ describe('MATCH keyword', () => {
assert.parseOk(statement, 'MATCH (a:Node)-[e:Edge]->(b:Node)', actual => { assert.parseOk(statement, 'MATCH (a:Node)-[e:Edge]->(b:Node)', actual => {
const expected = [ const expected = [
makeNode('a', 'Node'), makeNode('a', 'Node'),
makeEdge('e', 'Edge'), makeDirectedEdge('e', 'Edge', 1),
makeNode('b', 'Node'), makeNode('b', 'Node'),
] ]
@ -108,7 +108,7 @@ describe('MATCH keyword', () => {
assert.parseOk(statement, 'MATCH (a:Node { db: 0.7 })-[e:Edge]->(b:Node { db: 0.95 })', actual => { assert.parseOk(statement, 'MATCH (a:Node { db: 0.7 })-[e:Edge]->(b:Node { db: 0.95 })', actual => {
const expected = [ const expected = [
makeNode('a', 'Node', [['db', 0.7]]), makeNode('a', 'Node', [['db', 0.7]]),
makeEdge('e', 'Edge'), makeDirectedEdge('e', 'Edge', 1),
makeNode('b', 'Node', [['db', 0.95]]), makeNode('b', 'Node', [['db', 0.95]]),
] ]
@ -118,7 +118,7 @@ describe('MATCH keyword', () => {
assert.parseOk(statement, 'MATCH (:Node { db: 0.7 })-[:Edge]->(:Node { db: 0.95 })', actual => { assert.parseOk(statement, 'MATCH (:Node { db: 0.7 })-[:Edge]->(:Node { db: 0.95 })', actual => {
const expected = [ const expected = [
makeNode(undefined, 'Node', [['db', 0.7]]), makeNode(undefined, 'Node', [['db', 0.7]]),
makeEdge(undefined, 'Edge'), makeDirectedEdge(undefined, 'Edge', 1),
makeNode(undefined, 'Node', [['db', 0.95]]), makeNode(undefined, 'Node', [['db', 0.95]]),
] ]
@ -130,9 +130,9 @@ describe('MATCH keyword', () => {
assert.parseOk(statement, 'MATCH (player:Player)-[:Knows]->(a:NPC)-[:Knows]->(b:NPC)', actual => { assert.parseOk(statement, 'MATCH (player:Player)-[:Knows]->(a:NPC)-[:Knows]->(b:NPC)', actual => {
const expected = [ const expected = [
makeNode('player', 'Player'), makeNode('player', 'Player'),
makeEdge(undefined, 'Knows'), makeDirectedEdge(undefined, 'Knows', 1),
makeNode('a', 'NPC'), makeNode('a', 'NPC'),
makeEdge(undefined, 'Knows'), makeDirectedEdge(undefined, 'Knows', 1),
makeNode('b', 'NPC'), makeNode('b', 'NPC'),
] ]

View file

@ -1,4 +1,4 @@
import { KeyValuePair, Name, Label, Identifier, Literal, Edge, Node } from '../src/query/types.js' import { KeyValuePair, Name, Label, Identifier, Literal, Edge, Node, DirectedEdge } from '../src/query/types.js'
export const keyValuePair = ([k, v]) => new KeyValuePair(new Name(new Identifier(k)), new Literal(v)) export const keyValuePair = ([k, v]) => new KeyValuePair(new Name(new Identifier(k)), new Literal(v))
@ -9,5 +9,6 @@ export const graphObject = (name, label, props = [], Type = Node) => new Type(
) )
export const makeNode = graphObject export const makeNode = graphObject
export const makeEdge = (name, label, props) => graphObject(name, label, props, Edge) export const makeEdge = (name, label, props = []) => graphObject(name, label, props, Edge)
export const makeDirectedEdge = (name, label, direction, props = []) => DirectedEdge.fromEdge(makeEdge(name, label, props), direction)