Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
#!/usr/bin/env node
/**
* Debug key format mismatch in SPARQL triple store
*/
import { readFile } from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
async function test() {
console.log('=== Debug Key Format Mismatch ===\n');
// Load WASM
const wasmPath = join(__dirname, '../public/pkg/rvlite_bg.wasm');
const wasmBytes = await readFile(wasmPath);
const rvliteModule = await import('../public/pkg/rvlite.js');
const { default: initRvLite, RvLite, RvLiteConfig } = rvliteModule;
await initRvLite(wasmBytes);
const config = new RvLiteConfig(384);
const db = new RvLite(config);
console.log('✓ WASM initialized');
// Test 1: Check what format the add_triple expects
console.log('\n=== Test 1: Adding triples with different formats ===');
// Format A: With angle brackets
try {
db.add_triple('<http://ex.org/a1>', '<http://ex.org/p1>', '<http://ex.org/o1>');
console.log('✓ Format A (with <>) accepted');
} catch (e) {
console.log('✗ Format A (with <>) rejected:', e.message);
}
// Format B: Without angle brackets
try {
db.add_triple('http://ex.org/a2', 'http://ex.org/p2', 'http://ex.org/o2');
console.log('✓ Format B (without <>) accepted');
} catch (e) {
console.log('✗ Format B (without <>) rejected:', e.message);
}
console.log(`Total triples: ${db.triple_count()}`);
// Test 2: Try SPARQL queries that match each format
console.log('\n=== Test 2: SPARQL queries with different predicate formats ===');
const testQueries = [
// Query for triples added with format A
'SELECT ?s WHERE { ?s <http://ex.org/p1> ?o }',
// Query for triples added with format B
'SELECT ?s WHERE { ?s <http://ex.org/p2> ?o }',
// Wildcard predicate (variable)
// 'SELECT ?s ?p WHERE { ?s ?p ?o }', // This fails with "Complex property paths not yet supported"
];
for (const query of testQueries) {
console.log(`\nQuery: ${query}`);
try {
const result = db.sparql(query);
console.log('Result:', JSON.stringify(result, null, 2));
} catch (e) {
console.log('ERROR:', e.message || e);
}
}
// Test 3: Add rdf:type triple and test with actual RDF type query
console.log('\n=== Test 3: RDF type query ===');
db.add_triple(
'<http://example.org/Alice>',
'<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>',
'<http://example.org/Person>'
);
console.log(`Triple count after adding rdf:type: ${db.triple_count()}`);
const typeQuery = 'SELECT ?s WHERE { ?s <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Person> }';
console.log(`Query: ${typeQuery}`);
try {
const result = db.sparql(typeQuery);
console.log('Result:', JSON.stringify(result, null, 2));
if (result.bindings && result.bindings.length > 0) {
console.log('✓ SPARQL is working!');
} else {
console.log('✗ No bindings returned - key mismatch suspected');
}
} catch (e) {
console.log('ERROR:', e.message || e);
}
// Test 4: Simple triple with known data
console.log('\n=== Test 4: Minimal test case ===');
db.add_triple('<http://a>', '<http://b>', '<http://c>');
console.log(`Triple count: ${db.triple_count()}`);
const minimalQuery = 'SELECT ?s WHERE { ?s <http://b> ?o }';
console.log(`Query: ${minimalQuery}`);
try {
const result = db.sparql(minimalQuery);
console.log('Result:', JSON.stringify(result, null, 2));
} catch (e) {
console.log('ERROR:', e.message || e);
}
// Test 5: Get all triples using 'a' keyword (rdf:type shortcut)
console.log('\n=== Test 5: Using "a" keyword ===');
const aQuery = 'SELECT ?s WHERE { ?s a <http://example.org/Person> }';
console.log(`Query: ${aQuery}`);
try {
const result = db.sparql(aQuery);
console.log('Result:', JSON.stringify(result, null, 2));
} catch (e) {
console.log('ERROR:', e.message || e);
}
db.free();
}
test().catch(console.error);

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env node
/**
* Debug SPARQL execution to understand why results are empty
*/
import { readFile } from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
async function test() {
console.log('=== Debug SPARQL Execution ===\n');
// Load WASM
const wasmPath = join(__dirname, '../public/pkg/rvlite_bg.wasm');
const wasmBytes = await readFile(wasmPath);
const rvliteModule = await import('../public/pkg/rvlite.js');
const { default: initRvLite, RvLite, RvLiteConfig } = rvliteModule;
await initRvLite(wasmBytes);
const config = new RvLiteConfig(384);
const db = new RvLite(config);
console.log('✓ WASM initialized');
// Add triples
console.log('\n=== Adding Triples ===');
const triples = [
['<http://example.org/Alice>', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<http://example.org/Person>'],
['<http://example.org/Alice>', '<http://example.org/name>', '"Alice"'],
['<http://example.org/Bob>', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<http://example.org/Person>'],
['<http://example.org/Bob>', '<http://example.org/name>', '"Bob"'],
['<http://example.org/Alice>', '<http://example.org/knows>', '<http://example.org/Bob>'],
];
for (const [s, p, o] of triples) {
try {
db.add_triple(s, p, o);
console.log(` Added: ${s} ${p} ${o}`);
} catch (e) {
console.log(` ERROR adding triple: ${e.message}`);
}
}
console.log(`Triple count: ${db.triple_count()}`);
// Test queries with full debug output
console.log('\n=== Testing SPARQL Queries ===');
const queries = [
// Simple SELECT with variable predicate
"SELECT ?s ?p ?o WHERE { ?s ?p ?o }",
// SELECT with specific predicate
"SELECT ?s WHERE { ?s <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Person> }",
// SELECT with specific predicate (no angle brackets in predicate)
"SELECT ?s WHERE { ?s <http://example.org/knows> ?o }",
// ASK query
"ASK { <http://example.org/Alice> <http://example.org/knows> <http://example.org/Bob> }",
];
for (const query of queries) {
console.log(`\nQuery: ${query}`);
try {
const result = db.sparql(query);
console.log('Result type:', typeof result);
console.log('Result:', JSON.stringify(result, null, 2));
} catch (e) {
console.log('ERROR:', e.message || e);
}
}
db.free();
}
test().catch(console.error);

View File

@@ -0,0 +1,318 @@
#!/usr/bin/env node
/**
* Comprehensive E2E Test for RvLite WASM
* Tests: Vector API, SPARQL, Cypher, SQL
*/
import { readFile } from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
// Test results tracking
const results = {
passed: 0,
failed: 0,
tests: []
};
function test(name, fn) {
return async () => {
try {
await fn();
results.passed++;
results.tests.push({ name, status: 'PASS' });
console.log(` \x1b[32m✓\x1b[0m ${name}`);
} catch (e) {
results.failed++;
results.tests.push({ name, status: 'FAIL', error: e.message });
console.log(` \x1b[31m✗\x1b[0m ${name}`);
console.log(` Error: ${e.message}`);
}
};
}
function assert(condition, message) {
if (!condition) throw new Error(message || 'Assertion failed');
}
function assertEqual(actual, expected, message) {
if (actual !== expected) {
throw new Error(message || `Expected ${expected}, got ${actual}`);
}
}
async function runTests() {
console.log('╔════════════════════════════════════════════════════════════╗');
console.log('║ RvLite WASM Comprehensive E2E Test Suite ║');
console.log('╚════════════════════════════════════════════════════════════╝\n');
// Load WASM
console.log('Loading WASM module...');
const wasmPath = join(__dirname, '../public/pkg/rvlite_bg.wasm');
const wasmBytes = await readFile(wasmPath);
const rvliteModule = await import('../public/pkg/rvlite.js');
const { default: initRvLite, RvLite, RvLiteConfig } = rvliteModule;
await initRvLite(wasmBytes);
console.log('WASM module loaded successfully!\n');
const config = new RvLiteConfig(384);
const db = new RvLite(config);
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 1: VECTOR API TESTS
// ═══════════════════════════════════════════════════════════════════════════
console.log('═══════════════════════════════════════════════════════════════');
console.log('SECTION 1: Vector API Tests');
console.log('═══════════════════════════════════════════════════════════════');
await test('Vector insert works', async () => {
const vector = Array.from({ length: 384 }, () => Math.random());
const id = db.insert(vector, { label: 'test-vector-1' });
assert(id !== undefined && id !== null, 'Insert should return an ID');
})();
await test('Vector multiple inserts work', async () => {
const insertedIds = [];
for (let i = 0; i < 10; i++) {
const vector = Array.from({ length: 384 }, () => Math.random());
const id = db.insert(vector, { index: i, batch: 'batch-1' });
insertedIds.push(id);
}
assertEqual(insertedIds.length, 10, 'Should insert 10 vectors');
})();
await test('Vector search returns results', async () => {
const query = Array.from({ length: 384 }, () => Math.random());
const results = db.search(query, 5);
assert(Array.isArray(results), 'Search should return array');
assert(results.length > 0, 'Should find vectors');
assert(results[0].id !== undefined, 'Results should have IDs');
assert(results[0].score !== undefined, 'Results should have scores');
})();
await test('Vector count is correct', async () => {
const count = db.len();
assert(count >= 11, 'Should have at least 11 vectors');
})();
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 2: SPARQL TESTS
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n═══════════════════════════════════════════════════════════════');
console.log('SECTION 2: SPARQL Tests');
console.log('═══════════════════════════════════════════════════════════════');
// Add RDF triples for testing
const rdfTriples = [
// People
['<http://example.org/Alice>', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<http://example.org/Person>'],
['<http://example.org/Alice>', '<http://example.org/name>', '"Alice Smith"'],
['<http://example.org/Alice>', '<http://example.org/age>', '"30"'],
['<http://example.org/Alice>', '<http://example.org/email>', '"alice@example.org"'],
['<http://example.org/Bob>', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<http://example.org/Person>'],
['<http://example.org/Bob>', '<http://example.org/name>', '"Bob Jones"'],
['<http://example.org/Bob>', '<http://example.org/age>', '"25"'],
['<http://example.org/Carol>', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<http://example.org/Person>'],
['<http://example.org/Carol>', '<http://example.org/name>', '"Carol White"'],
// Relationships
['<http://example.org/Alice>', '<http://example.org/knows>', '<http://example.org/Bob>'],
['<http://example.org/Alice>', '<http://example.org/knows>', '<http://example.org/Carol>'],
['<http://example.org/Bob>', '<http://example.org/knows>', '<http://example.org/Carol>'],
// Projects
['<http://example.org/ProjectX>', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<http://example.org/Project>'],
['<http://example.org/ProjectX>', '<http://example.org/name>', '"Project X"'],
['<http://example.org/Alice>', '<http://example.org/worksOn>', '<http://example.org/ProjectX>'],
['<http://example.org/Bob>', '<http://example.org/worksOn>', '<http://example.org/ProjectX>'],
];
await test('SPARQL: Add triples', async () => {
for (const [s, p, o] of rdfTriples) {
db.add_triple(s, p, o);
}
const count = db.triple_count();
assert(count >= rdfTriples.length, `Should have at least ${rdfTriples.length} triples`);
})();
await test('SPARQL: SELECT with rdf:type', async () => {
const result = db.sparql('SELECT ?person WHERE { ?person <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Person> }');
assertEqual(result.type, 'select', 'Should be SELECT result');
assert(result.bindings.length >= 3, 'Should find at least 3 people');
assert(result.bindings.some(b => b.person.value === 'http://example.org/Alice'), 'Should find Alice');
})();
await test('SPARQL: SELECT with "a" keyword (rdf:type shortcut)', async () => {
const result = db.sparql('SELECT ?project WHERE { ?project a <http://example.org/Project> }');
assertEqual(result.type, 'select', 'Should be SELECT result');
assert(result.bindings.length >= 1, 'Should find at least 1 project');
})();
await test('SPARQL: SELECT with specific predicate', async () => {
const result = db.sparql('SELECT ?who WHERE { <http://example.org/Alice> <http://example.org/knows> ?who }');
assertEqual(result.type, 'select', 'Should be SELECT result');
assertEqual(result.bindings.length, 2, 'Alice knows 2 people');
})();
await test('SPARQL: ASK query (true case)', async () => {
const result = db.sparql('ASK { <http://example.org/Alice> <http://example.org/knows> <http://example.org/Bob> }');
assertEqual(result.type, 'ask', 'Should be ASK result');
assertEqual(result.result, true, 'Alice should know Bob');
})();
await test('SPARQL: ASK query (false case)', async () => {
const result = db.sparql('ASK { <http://example.org/Carol> <http://example.org/knows> <http://example.org/Alice> }');
assertEqual(result.type, 'ask', 'Should be ASK result');
assertEqual(result.result, false, 'Carol does not know Alice');
})();
await test('SPARQL: SELECT with LIMIT', async () => {
const result = db.sparql('SELECT ?s WHERE { ?s a <http://example.org/Person> } LIMIT 2');
assertEqual(result.type, 'select', 'Should be SELECT result');
assertEqual(result.bindings.length, 2, 'Should return exactly 2 results');
})();
await test('SPARQL: SELECT with literal values', async () => {
const result = db.sparql('SELECT ?name WHERE { <http://example.org/Alice> <http://example.org/name> ?name }');
assertEqual(result.type, 'select', 'Should be SELECT result');
assertEqual(result.bindings.length, 1, 'Should find Alice\'s name');
assertEqual(result.bindings[0].name.type, 'literal', 'Name should be literal');
})();
await test('SPARQL: Result binding format (IRI)', async () => {
const result = db.sparql('SELECT ?s WHERE { ?s a <http://example.org/Person> } LIMIT 1');
const binding = result.bindings[0];
assertEqual(binding.s.type, 'iri', 'Should have type=iri');
assert(binding.s.value.startsWith('http://'), 'Value should be clean IRI');
})();
await test('SPARQL: Result binding format (Literal)', async () => {
const result = db.sparql('SELECT ?name WHERE { <http://example.org/Bob> <http://example.org/name> ?name }');
const binding = result.bindings[0];
assertEqual(binding.name.type, 'literal', 'Should have type=literal');
assert(binding.name.datatype, 'Should have datatype');
})();
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 3: CYPHER TESTS
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n═══════════════════════════════════════════════════════════════');
console.log('SECTION 3: Cypher Tests');
console.log('═══════════════════════════════════════════════════════════════');
await test('Cypher: CREATE node', async () => {
const result = db.cypher('CREATE (n:TestNode {name: "TestCypher"}) RETURN n');
assert(result !== undefined, 'Should return result');
})();
await test('Cypher: MATCH query', async () => {
const result = db.cypher('MATCH (n:TestNode) RETURN n.name');
assert(result !== undefined, 'Should return result');
})();
await test('Cypher: CREATE relationship', async () => {
db.cypher('CREATE (a:CypherPerson {name: "Dave"})');
db.cypher('CREATE (b:CypherPerson {name: "Eve"})');
const result = db.cypher('MATCH (a:CypherPerson {name: "Dave"}), (b:CypherPerson {name: "Eve"}) CREATE (a)-[r:KNOWS]->(b) RETURN r');
assert(result !== undefined, 'Should create relationship');
})();
await test('Cypher: MATCH with relationship', async () => {
const result = db.cypher('MATCH (a:CypherPerson)-[r:KNOWS]->(b:CypherPerson) RETURN a.name, b.name');
assert(result !== undefined, 'Should match relationships');
})();
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 4: DATABASE INFO & STATS
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n═══════════════════════════════════════════════════════════════');
console.log('SECTION 4: Database Statistics');
console.log('═══════════════════════════════════════════════════════════════');
await test('Get vector count', async () => {
const count = db.len();
console.log(` Vector count: ${count}`);
assert(count >= 0, 'Should return valid count');
})();
await test('Get triple count', async () => {
const count = db.triple_count();
console.log(` Triple count: ${count}`);
assert(count >= rdfTriples.length, 'Should return valid count');
})();
await test('Get database config', async () => {
const config = db.get_config();
assert(config.dimensions, 'Should have dimensions');
const version = db.get_version();
assert(version, 'Should have version');
const features = db.get_features();
assert(features, 'Should have features');
console.log(` Version: ${version}`);
console.log(` Dimensions: ${config.dimensions}`);
console.log(` Distance metric: ${config.distance_metric}`);
})();
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 5: EDGE CASES & ERROR HANDLING
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n═══════════════════════════════════════════════════════════════');
console.log('SECTION 5: Edge Cases & Error Handling');
console.log('═══════════════════════════════════════════════════════════════');
await test('SPARQL: Empty result for non-existent data', async () => {
const result = db.sparql('SELECT ?s WHERE { ?s a <http://example.org/NonExistent> }');
assertEqual(result.bindings.length, 0, 'Should return empty bindings');
})();
await test('SPARQL: Handle special characters in IRIs', async () => {
db.add_triple('<http://example.org/item#1>', '<http://example.org/type>', '<http://example.org/Thing>');
const result = db.sparql('SELECT ?s WHERE { ?s <http://example.org/type> <http://example.org/Thing> }');
assert(result.bindings.length >= 1, 'Should handle # in IRIs');
})();
await test('Vector: Search with empty database returns empty array', async () => {
// Create fresh instance for this test
const freshConfig = new RvLiteConfig(64);
const freshDb = new RvLite(freshConfig);
const query = Array.from({ length: 64 }, () => Math.random());
const searchResults = freshDb.search(query, 5);
assert(Array.isArray(searchResults), 'Should return array');
assertEqual(searchResults.length, 0, 'Should return empty array');
freshDb.free();
})();
// ═══════════════════════════════════════════════════════════════════════════
// SUMMARY
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n╔════════════════════════════════════════════════════════════╗');
console.log('║ TEST SUMMARY ║');
console.log('╚════════════════════════════════════════════════════════════╝');
console.log(`\n Total: ${results.passed + results.failed} tests`);
console.log(` \x1b[32mPassed: ${results.passed}\x1b[0m`);
console.log(` \x1b[31mFailed: ${results.failed}\x1b[0m`);
if (results.failed > 0) {
console.log('\n\x1b[31mFailed Tests:\x1b[0m');
results.tests.filter(t => t.status === 'FAIL').forEach(t => {
console.log(` - ${t.name}: ${t.error}`);
});
}
console.log('\n');
// Cleanup
db.free();
// Exit with appropriate code
process.exit(results.failed > 0 ? 1 : 0);
}
runTests().catch(err => {
console.error('Test suite crashed:', err);
process.exit(1);
});

View File

@@ -0,0 +1,409 @@
#!/usr/bin/env node
/**
* Comprehensive RvLite WASM Test Suite
* Tests ALL features: Vector API, SQL, SPARQL, Cypher
*
* Run with: node scripts/test-all.mjs
*/
import { readFile } from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
// Test results tracking
const results = {
total: 0,
passed: 0,
failed: 0,
sections: [],
errors: []
};
// Colors for terminal output
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
dim: '\x1b[2m',
bold: '\x1b[1m'
};
function log(color, text) {
console.log(`${color}${text}${colors.reset}`);
}
// Test runner
async function test(name, fn) {
results.total++;
try {
await fn();
results.passed++;
console.log(` ${colors.green}${colors.reset} ${name}`);
return true;
} catch (e) {
results.failed++;
results.errors.push({ name, error: e.message });
console.log(` ${colors.red}${colors.reset} ${name}`);
console.log(` ${colors.dim}Error: ${e.message}${colors.reset}`);
return false;
}
}
function assert(condition, message) {
if (!condition) throw new Error(message || 'Assertion failed');
}
function assertEqual(actual, expected, message) {
if (actual !== expected) {
throw new Error(message || `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
}
}
function assertDeepIncludes(obj, key) {
if (typeof obj !== 'object' || obj === null || !(key in obj)) {
throw new Error(`Object should have key '${key}', got: ${JSON.stringify(obj)}`);
}
}
// Section header
function section(name) {
console.log(`\n${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`);
console.log(`${colors.bold}${name}${colors.reset}`);
console.log(`${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`);
results.sections.push({ name, startIndex: results.total });
}
async function runTests() {
console.log(`${colors.cyan}╔════════════════════════════════════════════════════════════╗${colors.reset}`);
console.log(`${colors.cyan}${colors.reset} ${colors.bold}RvLite WASM Comprehensive Test Suite${colors.reset} ${colors.cyan}${colors.reset}`);
console.log(`${colors.cyan}${colors.reset} Tests: Vector API • SQL • SPARQL • Cypher ${colors.cyan}${colors.reset}`);
console.log(`${colors.cyan}╚════════════════════════════════════════════════════════════╝${colors.reset}\n`);
// Load WASM
console.log('Loading WASM module...');
const wasmPath = join(__dirname, '../public/pkg/rvlite_bg.wasm');
const wasmBytes = await readFile(wasmPath);
const rvliteModule = await import('../public/pkg/rvlite.js');
const { default: initRvLite, RvLite, RvLiteConfig } = rvliteModule;
await initRvLite(wasmBytes);
const version = 'RvLite loaded';
console.log(`${colors.green}${colors.reset} ${version}\n`);
const config = new RvLiteConfig(128);
const db = new RvLite(config);
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 1: INITIALIZATION & CONFIG
// ═══════════════════════════════════════════════════════════════════════════
section('SECTION 1: Initialization & Configuration');
await test('RvLite instance created', () => {
assert(db !== null && db !== undefined, 'DB should be created');
});
await test('is_ready returns true', () => {
assert(db.is_ready() === true, 'Should be ready');
});
await test('get_version returns string', () => {
const version = db.get_version();
assert(typeof version === 'string' && version.length > 0, 'Should have version');
});
await test('get_features returns array', () => {
const features = db.get_features();
assert(Array.isArray(features), 'Features should be array');
});
await test('get_config returns valid config', () => {
const cfg = db.get_config();
assert(cfg !== null, 'Config should exist');
});
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 2: VECTOR API
// ═══════════════════════════════════════════════════════════════════════════
section('SECTION 2: Vector API');
let vectorId;
await test('Insert vector returns ID', () => {
const vector = new Float32Array(128).fill(0.5);
vectorId = db.insert(vector, { label: 'test-1' });
assert(vectorId !== null && vectorId !== undefined, 'Should return ID');
});
await test('Insert multiple vectors', () => {
for (let i = 0; i < 5; i++) {
const vector = new Float32Array(128).map(() => Math.random());
db.insert(vector, { index: i });
}
assert(db.len() >= 6, 'Should have at least 6 vectors');
});
await test('Search returns results', () => {
const query = new Float32Array(128).fill(0.5);
const results = db.search(query, 3);
assert(Array.isArray(results), 'Should return array');
assert(results.length > 0, 'Should find results');
});
await test('Search results have id and score', () => {
const query = new Float32Array(128).fill(0.5);
const results = db.search(query, 1);
assert(results[0].id !== undefined, 'Should have id');
assert(results[0].score !== undefined, 'Should have score');
});
await test('len() returns correct count', () => {
const count = db.len();
assert(typeof count === 'number' && count >= 6, 'Should have count >= 6');
});
await test('is_empty() returns false after inserts', () => {
assert(db.is_empty() === false, 'Should not be empty');
});
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 3: SQL (Vector Search)
// ═══════════════════════════════════════════════════════════════════════════
section('SECTION 3: SQL (Vector Search)');
await test('SQL: DROP TABLE (cleanup)', () => {
try {
const result = db.sql('DROP TABLE test_docs');
assert(result !== undefined, 'Should return result');
} catch {
// Table might not exist - that's OK
}
});
await test('SQL: CREATE TABLE with VECTOR', () => {
const result = db.sql('CREATE TABLE test_docs (id TEXT, title TEXT, embedding VECTOR(3))');
assert(result !== undefined, 'Should return result');
assertDeepIncludes(result, 'rows');
});
await test('SQL: INSERT vector data', () => {
const result = db.sql("INSERT INTO test_docs (id, title, embedding) VALUES ('d1', 'First Doc', [1.0, 2.0, 3.0])");
assert(result !== undefined, 'Should return result');
});
await test('SQL: INSERT multiple rows', () => {
db.sql("INSERT INTO test_docs (id, title, embedding) VALUES ('d2', 'Second Doc', [4.0, 5.0, 6.0])");
db.sql("INSERT INTO test_docs (id, title, embedding) VALUES ('d3', 'Third Doc', [7.0, 8.0, 9.0])");
});
await test('SQL: Vector search with L2 distance (<->)', () => {
const result = db.sql('SELECT * FROM test_docs ORDER BY embedding <-> [1.0, 2.0, 3.0] LIMIT 5');
assert(result.rows !== undefined, 'Should have rows');
assert(result.rows.length > 0, 'Should return results');
});
await test('SQL: Vector search with cosine distance (<=>)', () => {
const result = db.sql('SELECT * FROM test_docs ORDER BY embedding <=> [0.5, 0.5, 0.5] LIMIT 3');
assert(result.rows !== undefined, 'Should have rows');
});
await test('SQL: Vector search with WHERE filter', () => {
const result = db.sql("SELECT * FROM test_docs WHERE id = 'd1' ORDER BY embedding <-> [1.0, 2.0, 3.0] LIMIT 5");
assert(result.rows !== undefined, 'Should have rows');
});
await test('SQL: Non-vector SELECT (table scan)', () => {
const result = db.sql('SELECT * FROM test_docs');
assert(result.rows !== undefined, 'Should have rows');
assert(result.rows.length >= 3, 'Should return all 3 inserted rows');
});
await test('SQL: Results contain actual data (not empty objects)', () => {
const result = db.sql('SELECT * FROM test_docs ORDER BY embedding <-> [1.0, 2.0, 3.0] LIMIT 1');
assert(result.rows.length > 0, 'Should have rows');
const row = result.rows[0];
// Check that row is not empty {}
const keys = Object.keys(row);
assert(keys.length > 0, 'Row should have properties, not be empty {}');
});
await test('SQL: DROP TABLE', () => {
const result = db.sql('DROP TABLE test_docs');
assert(result !== undefined, 'Should return result');
});
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 4: SPARQL (RDF Triple Store)
// ═══════════════════════════════════════════════════════════════════════════
section('SECTION 4: SPARQL (RDF Triple Store)');
await test('SPARQL: Add triple', () => {
db.add_triple('<http://example.org/Alice>', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<http://example.org/Person>');
db.add_triple('<http://example.org/Alice>', '<http://example.org/name>', '"Alice Smith"');
db.add_triple('<http://example.org/Bob>', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<http://example.org/Person>');
db.add_triple('<http://example.org/Alice>', '<http://example.org/knows>', '<http://example.org/Bob>');
});
await test('SPARQL: triple_count() > 0', () => {
const count = db.triple_count();
assert(count >= 4, `Should have at least 4 triples, got ${count}`);
});
await test('SPARQL: SELECT query', () => {
const result = db.sparql('SELECT ?person WHERE { ?person <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Person> }');
assertEqual(result.type, 'select', 'Should be SELECT result');
assert(result.bindings.length >= 2, 'Should find at least 2 people');
});
await test('SPARQL: SELECT with "a" keyword (rdf:type shortcut)', () => {
const result = db.sparql('SELECT ?s WHERE { ?s a <http://example.org/Person> }');
assertEqual(result.type, 'select', 'Should be SELECT result');
assert(result.bindings.length >= 2, 'Should find people');
});
await test('SPARQL: ASK query (true case)', () => {
const result = db.sparql('ASK { <http://example.org/Alice> <http://example.org/knows> <http://example.org/Bob> }');
assertEqual(result.type, 'ask', 'Should be ASK result');
assertEqual(result.result, true, 'Alice should know Bob');
});
await test('SPARQL: ASK query (false case)', () => {
const result = db.sparql('ASK { <http://example.org/Bob> <http://example.org/knows> <http://example.org/Alice> }');
assertEqual(result.type, 'ask', 'Should be ASK result');
assertEqual(result.result, false, 'Bob does not know Alice');
});
await test('SPARQL: SELECT with LIMIT', () => {
const result = db.sparql('SELECT ?s WHERE { ?s a <http://example.org/Person> } LIMIT 1');
assertEqual(result.bindings.length, 1, 'Should return exactly 1 result');
});
await test('SPARQL: Result binding has type and value', () => {
const result = db.sparql('SELECT ?s WHERE { ?s a <http://example.org/Person> } LIMIT 1');
const binding = result.bindings[0];
assert(binding.s !== undefined, 'Should have binding');
assert(binding.s.type !== undefined, 'Should have type');
assert(binding.s.value !== undefined, 'Should have value');
});
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 5: CYPHER (Graph Database)
// ═══════════════════════════════════════════════════════════════════════════
section('SECTION 5: Cypher (Graph Database)');
await test('Cypher: CREATE node', () => {
const result = db.cypher("CREATE (n:TestPerson {name: 'Charlie', age: 35})");
assert(result !== undefined, 'Should return result');
});
await test('Cypher: CREATE multiple nodes', () => {
db.cypher("CREATE (n:TestPerson {name: 'Diana', age: 28})");
db.cypher("CREATE (c:TestCompany {name: 'Acme Inc', founded: 2010})");
});
await test('Cypher: MATCH query', () => {
const result = db.cypher('MATCH (n:TestPerson) RETURN n');
assert(result !== undefined, 'Should return result');
});
await test('Cypher: cypher_stats returns counts', () => {
const stats = db.cypher_stats();
assert(stats !== undefined, 'Should return stats');
// Stats might have node_count or nodes depending on version
});
await test('Cypher: CREATE relationship', () => {
const result = db.cypher("MATCH (a:TestPerson {name: 'Charlie'}), (b:TestCompany {name: 'Acme Inc'}) CREATE (a)-[r:WORKS_AT]->(b) RETURN r");
assert(result !== undefined, 'Should return result');
});
await test('Cypher: MATCH with relationship', () => {
const result = db.cypher('MATCH (a:TestPerson)-[r:WORKS_AT]->(b:TestCompany) RETURN a, b');
assert(result !== undefined, 'Should return result');
});
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 6: EDGE CASES & ERROR HANDLING
// ═══════════════════════════════════════════════════════════════════════════
section('SECTION 6: Edge Cases & Error Handling');
await test('SPARQL: Empty result for non-existent data', () => {
const result = db.sparql('SELECT ?s WHERE { ?s a <http://example.org/NonExistent> }');
assertEqual(result.bindings.length, 0, 'Should return empty bindings');
});
await test('SQL: Error on non-existent table', () => {
try {
db.sql('SELECT * FROM nonexistent_table ORDER BY col <-> [1,2,3] LIMIT 1');
throw new Error('Should have thrown');
} catch (e) {
assert(e.message.includes('not found') || e.message.includes('does not exist') || e.message !== 'Should have thrown',
'Should throw table not found error');
}
});
await test('Fresh instance search returns empty', () => {
const freshConfig = new RvLiteConfig(32);
const freshDb = new RvLite(freshConfig);
const query = new Float32Array(32).fill(0.5);
const searchResults = freshDb.search(query, 5);
assertEqual(searchResults.length, 0, 'Should return empty array');
freshDb.free();
});
// ═══════════════════════════════════════════════════════════════════════════
// SECTION 7: DATA PERSISTENCE METHODS
// ═══════════════════════════════════════════════════════════════════════════
section('SECTION 7: Data Persistence Methods');
await test('export_json returns object', () => {
const exported = db.export_json();
assert(typeof exported === 'object', 'Should return object');
});
// Cleanup
db.free();
// ═══════════════════════════════════════════════════════════════════════════
// SUMMARY
// ═══════════════════════════════════════════════════════════════════════════
console.log(`\n${colors.cyan}╔════════════════════════════════════════════════════════════╗${colors.reset}`);
console.log(`${colors.cyan}${colors.reset} ${colors.bold}TEST SUMMARY${colors.reset} ${colors.cyan}${colors.reset}`);
console.log(`${colors.cyan}╚════════════════════════════════════════════════════════════╝${colors.reset}`);
console.log(`\n Total: ${results.total} tests`);
console.log(` ${colors.green}Passed: ${results.passed}${colors.reset}`);
console.log(` ${colors.red}Failed: ${results.failed}${colors.reset}`);
if (results.failed > 0) {
console.log(`\n${colors.red}${colors.bold}Failed Tests:${colors.reset}`);
results.errors.forEach(({ name, error }) => {
console.log(` ${colors.red}${colors.reset} ${name}`);
console.log(` ${colors.dim}${error}${colors.reset}`);
});
}
// Per-section summary
console.log(`\n${colors.bold}Section Results:${colors.reset}`);
results.sections.forEach((sec, i) => {
const nextStart = results.sections[i + 1]?.startIndex || results.total;
const sectionTests = nextStart - sec.startIndex;
const icon = results.errors.some(e => {
const idx = results.errors.indexOf(e);
return idx >= sec.startIndex && idx < nextStart;
}) ? colors.yellow + '⚠' : colors.green + '✓';
console.log(` ${icon}${colors.reset} ${sec.name}`);
});
console.log('\n');
// Exit with appropriate code
process.exit(results.failed > 0 ? 1 : 0);
}
runTests().catch(err => {
console.error(`${colors.red}Test suite crashed:${colors.reset}`, err);
process.exit(1);
});

View File

@@ -0,0 +1,113 @@
#!/usr/bin/env node
/**
* SQL Test Script - Test CREATE TABLE and SQL operations in WASM
*/
import { readFile } from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
async function main() {
console.log('╔════════════════════════════════════════════════════════════╗');
console.log('║ RvLite WASM SQL Test Suite ║');
console.log('╚════════════════════════════════════════════════════════════╝\n');
// Load WASM
const wasmPath = join(__dirname, '../public/pkg/rvlite_bg.wasm');
const wasmBytes = await readFile(wasmPath);
const { default: initRvLite, RvLite, RvLiteConfig } = await import('../public/pkg/rvlite.js');
await initRvLite(wasmBytes);
console.log('WASM loaded successfully!\n');
const config = new RvLiteConfig(384);
const db = new RvLite(config);
const tests = [];
function test(name, fn) {
try {
fn();
tests.push({ name, passed: true });
console.log(` \x1b[32m✓\x1b[0m ${name}`);
} catch (error) {
tests.push({ name, passed: false, error: error.message });
console.log(` \x1b[31m✗\x1b[0m ${name}`);
console.log(` Error: ${error.message}`);
}
}
console.log('═══════════════════════════════════════════════════════════════');
console.log('SQL Parser Tests');
console.log('═══════════════════════════════════════════════════════════════');
// Test 1: CREATE TABLE with VECTOR column (3-dimensional for test)
test('CREATE TABLE with VECTOR', () => {
const result = db.sql("CREATE TABLE docs (id TEXT, content TEXT, embedding VECTOR(3))");
console.log(' Result:', JSON.stringify(result));
});
// Test 2: INSERT with correct vector dimensions
test('INSERT INTO table', () => {
const result = db.sql("INSERT INTO docs (id, content, embedding) VALUES ('doc1', 'hello world', [1.0, 2.0, 3.0])");
console.log(' Result:', JSON.stringify(result));
});
// Test 3: INSERT another vector
test('INSERT second vector', () => {
const result = db.sql("INSERT INTO docs (id, content, embedding) VALUES ('doc2', 'test content', [4.0, 5.0, 6.0])");
console.log(' Result:', JSON.stringify(result));
});
// Test 4: Vector search with L2 distance
test('Vector search with L2 distance', () => {
const result = db.sql("SELECT * FROM docs ORDER BY embedding <-> [1.0, 2.0, 3.0] LIMIT 5");
console.log(' Result:', JSON.stringify(result));
});
// Test 5: Vector search with cosine distance
test('Vector search with cosine distance', () => {
const result = db.sql("SELECT * FROM docs ORDER BY embedding <=> [0.5, 0.5, 0.5] LIMIT 5");
console.log(' Result:', JSON.stringify(result));
});
// Test 6: Vector search with filter
test('Vector search with filter', () => {
const result = db.sql("SELECT * FROM docs WHERE id = 'doc1' ORDER BY embedding <-> [1.0, 2.0, 3.0] LIMIT 5");
console.log(' Result:', JSON.stringify(result));
});
// Test 7: DROP TABLE
test('DROP TABLE', () => {
const result = db.sql("DROP TABLE docs");
console.log(' Result:', JSON.stringify(result));
});
// Cleanup
db.free();
// Summary
console.log('\n╔════════════════════════════════════════════════════════════╗');
console.log('║ TEST SUMMARY ║');
console.log('╚════════════════════════════════════════════════════════════╝\n');
const passed = tests.filter(t => t.passed).length;
const failed = tests.filter(t => !t.passed).length;
console.log(` Total: ${tests.length} tests`);
console.log(` \x1b[32mPassed: ${passed}\x1b[0m`);
console.log(` \x1b[31mFailed: ${failed}\x1b[0m`);
if (failed > 0) {
console.log('\n Failed tests:');
tests.filter(t => !t.passed).forEach(t => {
console.log(` - ${t.name}: ${t.error}`);
});
process.exit(1);
}
}
main().catch(console.error);