Multi-Database End-to-End Test Guide
This guide provides a test sequence to verify multi-database functionality, including in-place upgrade verification, database creation, data isolation, composite databases, and proper cleanup.
Prerequisites
Start NornicDB with an existing dataset (to verify in-place upgrade)
bash./nornicdb serveConnect to NornicDB using your preferred client:
- Cypher Shell:
./nornicdb cypher-shell - Web UI:
http://localhost:7474 - Bolt client:
bolt://localhost:7687
- Cypher Shell:
Important: Default Database Name
NornicDB uses "nornic" as the default database name (Neo4j uses "neo4j").
If you're using a Neo4j-compatible client that defaults to "neo4j", you have two options:
Option 1: Configure NornicDB to use "neo4j" as default (Recommended for Neo4j compatibility)
Via Config File:
database:
default_database: "neo4j"Via Environment Variable:
export NORNICDB_DEFAULT_DATABASE=neo4j
# Or Neo4j-compatible:
export NEO4J_dbms_default__database=neo4jVia Command Line:
./nornicdb serve --default-database neo4jThen restart NornicDB.
Option 2: Switch to "nornic" database in your client
In Web UI:
- Look for a database selector/dropdown in the UI
- Select
"nornic"from the list - Or use the connection URL:
http://localhost:7474/db/nornic/tx/commit
In Cypher Shell:
:USE nornicIn Drivers:
# Python
driver = GraphDatabase.driver(
"bolt://localhost:7687",
database="nornic" # Specify nornic instead of neo4j
)In HTTP API:
POST /db/nornic/tx/commitTest Sequence
Step 1: Verify In-Place Upgrade
Purpose: Confirm that existing data was automatically migrated to the default database namespace.
** Before running this query**: Make sure you're connected to the correct database:
- If you configured NornicDB to use
"neo4j"as default, you're already connected - If using default
"nornic", switch to it in your client (see Prerequisites above)
-- List all databases (should show default database and system database)
SHOW DATABASESExpected Result:
- Should show at least
nornic(orneo4jif you configured it) andsystem - The default database should be marked as
default: true - If you see an error "Database 'neo4j' not found", see Troubleshooting section below
-- Verify existing data is accessible in default database
-- (Assuming you have existing data - adjust query based on your dataset)
MATCH (n)
RETURN count(n) as node_countExpected Result:
- Should return the count of nodes from your existing dataset
- Confirms data was migrated to default database namespace
-- Check that we're in the default database context
-- (In Cypher Shell, you can also use :USE nornic to explicitly switch)
MATCH (n)
RETURN labels(n) as labels, count(n) as count
LIMIT 10Expected Result:
- Should show your existing data's labels and counts
- Confirms default database contains migrated data
Step 2: Create First Database
Purpose: Create a new database other than the default.
-- Create first test database
CREATE DATABASE test_db_aExpected Result:
- Success message or empty result (command succeeded)
-- Verify database was created
SHOW DATABASESExpected Result:
- Should now show
test_db_ain the list - Should show
nornic(default),system, andtest_db_a
Step 3: Insert Data in First Database
Purpose: Add test data to the first database.
-- Switch to first database
-- Note: In Cypher Shell, use :USE test_db_a
-- In drivers, specify database="test_db_a"
-- For this test, we'll use explicit database context
-- Create nodes in test_db_a
CREATE (alice:Person {name: "Alice", id: "a1", db: "test_db_a"})
CREATE (bob:Person {name: "Bob", id: "a2", db: "test_db_a"})
CREATE (company:Company {name: "Acme Corp", id: "a3", db: "test_db_a"})
CREATE (alice)-[:WORKS_FOR]->(company)
CREATE (bob)-[:WORKS_FOR]->(company)
RETURN alice, bob, companyExpected Result:
- Should create 3 nodes and 2 relationships
- Should return the created nodes
Note: If using Cypher Shell, you need to switch databases first:
:USE test_db_a
CREATE (alice:Person {name: "Alice", id: "a1", db: "test_db_a"})
CREATE (bob:Person {name: "Bob", id: "a2", db: "test_db_a"})
CREATE (company:Company {name: "Acme Corp", id: "a3", db: "test_db_a"})
CREATE (alice)-[:WORKS_FOR]->(company)
CREATE (bob)-[:WORKS_FOR]->(company)
RETURN alice, bob, companyStep 4: Query First Database
Purpose: Verify data exists in the first database and can be queried.
-- Query all nodes in test_db_a
:USE test_db_a
MATCH (n)
RETURN n.name as name, labels(n) as labels, n.db as db
ORDER BY n.nameExpected Result:
- Should return 3 rows: Alice, Acme Corp, Bob
- All should have
db: "test_db_a"
-- Query relationships
:USE test_db_a
MATCH (p:Person)-[r:WORKS_FOR]->(c:Company)
RETURN p.name as person, c.name as company, type(r) as relationshipExpected Result:
- Should return 2 rows: Alice -> Acme Corp, Bob -> Acme Corp
-- Count nodes by label
:USE test_db_a
MATCH (n)
RETURN labels(n)[0] as label, count(n) as count
ORDER BY labelExpected Result:
- Should return: Company: 1, Person: 2
Step 5: Create Second Database
Purpose: Create another database to test isolation.
-- Create second test database
CREATE DATABASE test_db_bExpected Result:
- Success message or empty result
-- Verify both databases exist
SHOW DATABASESExpected Result:
- Should show
test_db_a,test_db_b,nornic, andsystem
Step 6: Insert Data in Second Database
Purpose: Add different test data to the second database.
-- Switch to second database
-- Note: In Cypher Shell, use :USE test_db_b
-- Create nodes in test_db_b
:USE test_db_b
CREATE (charlie:Person {name: "Charlie", id: "b1", db: "test_db_b"})
CREATE (diana:Person {name: "Diana", id: "b2", db: "test_db_b"})
CREATE (order:Order {order_id: "ORD-001", amount: 1000, db: "test_db_b"})
CREATE (charlie)-[:PLACED]->(order)
CREATE (diana)-[:PLACED]->(order)
RETURN charlie, diana, orderExpected Result:
- Should create 3 nodes and 2 relationships
- Should return the created nodes
Note: If using Cypher Shell:
:USE test_db_b
CREATE (charlie:Person {name: "Charlie", id: "b1", db: "test_db_b"})
CREATE (diana:Person {name: "Diana", id: "b2", db: "test_db_b"})
CREATE (order:Order {order_id: "ORD-001", amount: 1000, db: "test_db_b"})
CREATE (charlie)-[:PLACED]->(order)
CREATE (diana)-[:PLACED]->(order)
RETURN charlie, diana, orderStep 7: Query Second Database
Purpose: Verify data exists in the second database.
-- Query all nodes in test_db_b
:USE test_db_b
MATCH (n)
RETURN n.name as name, n.order_id as order_id, labels(n) as labels, n.db as db
ORDER BY n.nameExpected Result:
- Should return 3 rows: Charlie, Diana, ORD-001
- All should have
db: "test_db_b"
-- Query relationships
:USE test_db_b
MATCH (p:Person)-[r:PLACED]->(o:Order)
RETURN p.name as person, o.order_id as order, type(r) as relationshipExpected Result:
- Should return 2 rows: Charlie -> ORD-001, Diana -> ORD-001
-- Count nodes by label
:USE test_db_b
MATCH (n)
RETURN labels(n)[0] as label, count(n) as count
ORDER BY labelExpected Result:
- Should return: Order: 1, Person: 2
Step 8: Verify Isolation
Purpose: Confirm that databases are isolated and don't see each other's data.
-- Switch back to test_db_a
-- Note: In Cypher Shell, use :USE test_db_a
-- Query test_db_a - should NOT see test_db_b data
:USE test_db_a
MATCH (n)
RETURN n.name as name, n.order_id as order_id, labels(n) as labels, n.db as db
ORDER BY n.nameExpected Result:
- Should return only: Alice, Acme Corp, Bob
- Should NOT return Charlie, Diana, or ORD-001
- All should have
db: "test_db_a"
-- Verify no Order nodes in test_db_a
:USE test_db_a
MATCH (o:Order)
RETURN count(o) as order_countExpected Result:
- Should return:
order_count: 0
-- Switch to test_db_b
-- Note: In Cypher Shell, use :USE test_db_b
-- Query test_db_b - should NOT see test_db_a data
:USE test_db_b
MATCH (n)
RETURN n.name as name, labels(n) as labels, n.db as db
ORDER BY n.nameExpected Result:
- Should return only: Charlie, Diana, ORD-001
- Should NOT return Alice, Bob, or Acme Corp
- All should have
db: "test_db_b"
-- Verify no Company nodes in test_db_b
:USE test_db_b
MATCH (c:Company)
RETURN count(c) as company_countExpected Result:
- Should return:
company_count: 0
-- Switch to default database (nornic)
-- Note: In Cypher Shell, use :USE nornic
-- Verify default database still has original data
:USE nornic
MATCH (n)
RETURN count(n) as node_countExpected Result:
- Should return the count of your original migrated data
- Should NOT include data from test_db_a or test_db_b
-- Verify default database doesn't see test databases' data
:USE nornic
MATCH (n)
WHERE n.db IN ["test_db_a", "test_db_b"]
RETURN count(n) as test_db_nodesExpected Result:
- Should return:
test_db_nodes: 0
Step 9: Create Composite Database
Purpose: Create a composite database that spans both test databases.
-- Create composite database
CREATE COMPOSITE DATABASE test_composite
ALIAS db_a FOR DATABASE test_db_a
ALIAS db_b FOR DATABASE test_db_bExpected Result:
- Success message or empty result
-- Verify composite database was created
SHOW COMPOSITE DATABASESExpected Result:
- Should show
test_compositein the list
-- Show constituents of composite database
SHOW CONSTITUENTS FOR COMPOSITE DATABASE test_compositeExpected Result:
- Should show 2 constituents:
db_a->test_db_a,db_b->test_db_b
Step 10: Query Composite Database
Purpose: Verify that composite database provides unified view across both databases.
-- Switch to composite database
-- Note: In Cypher Shell, use :USE test_composite
-- Query all Person nodes across both databases
MATCH (p:Person)
RETURN p.name as name, p.db as db, labels(p) as labels
ORDER BY p.nameExpected Result:
- Should return 4 rows: Alice, Bob, Charlie, Diana
- Should show
db: "test_db_a"for Alice and Bob - Should show
db: "test_db_b"for Charlie and Diana
-- Count all nodes across both databases
MATCH (n)
RETURN count(n) as total_nodesExpected Result:
- Should return:
total_nodes: 6(3 from test_db_a + 3 from test_db_b)
-- Count nodes by label across both databases
MATCH (n)
RETURN labels(n)[0] as label, count(n) as count
ORDER BY labelExpected Result:
- Should return: Company: 1, Order: 1, Person: 4
-- Query relationships across both databases
MATCH (p:Person)-[r]->(target)
RETURN p.name as person, type(r) as relationship, labels(target)[0] as target_type, target.name as target_name, target.order_id as order_id
ORDER BY p.nameExpected Result:
- Should return 4 rows:
- Alice -> WORKS_FOR -> Company (Acme Corp)
- Bob -> WORKS_FOR -> Company (Acme Corp)
- Charlie -> PLACED -> Order (ORD-001)
- Diana -> PLACED -> Order (ORD-001)
-- Test write operation (should route to appropriate constituent)
CREATE (new_person:Person {name: "Eve", id: "composite-1", db: "test_composite"})
RETURN new_personExpected Result:
- Should create a new Person node
- Node will be routed to one of the constituent databases
-- Verify new node exists in composite view
MATCH (p:Person {name: "Eve"})
RETURN p.name as name, p.db as dbExpected Result:
- Should return: Eve with
db: "test_composite"or the actual constituent database name
-- Verify schema merging (should show indexes/constraints from both constituents)
SHOW INDEXESExpected Result:
- Should show indexes from both test_db_a and test_db_b (if any were created)
-- Verify schema merging for constraints
SHOW CONSTRAINTSExpected Result:
- Should show constraints from both test_db_a and test_db_b (if any were created)
Step 11: Verify Composite Database Isolation
Purpose: Confirm that composite database doesn't affect individual databases.
-- Switch back to test_db_a
-- Note: In Cypher Shell, use :USE test_db_a
-- Verify test_db_a still has its original data
MATCH (n)
RETURN count(n) as node_countExpected Result:
- Should return:
node_count: 3(Alice, Bob, Acme Corp) - Should NOT include data from test_db_b
-- Switch to test_db_b
-- Note: In Cypher Shell, use :USE test_db_b
-- Verify test_db_b still has its original data
MATCH (n)
RETURN count(n) as node_countExpected Result:
- Should return:
node_count: 3(Charlie, Diana, ORD-001) - Should NOT include data from test_db_a
Step 12: Cleanup - Drop Composite Database
Purpose: Remove composite database first (before dropping constituent databases).
-- Drop composite database
DROP COMPOSITE DATABASE test_compositeExpected Result:
- Success message or empty result
-- Verify composite database was dropped
SHOW COMPOSITE DATABASESExpected Result:
- Should return empty result or not show
test_composite
-- Verify constituent databases still exist
SHOW DATABASESExpected Result:
- Should still show
test_db_aandtest_db_b - Confirms dropping composite doesn't affect constituents
Step 13: Cleanup - Drop Test Databases
Purpose: Remove test databases in proper order.
-- Drop first test database
DROP DATABASE test_db_aExpected Result:
- Success message or empty result
-- Verify first database was dropped
SHOW DATABASESExpected Result:
- Should NOT show
test_db_a - Should still show
test_db_b,nornic, andsystem
-- Drop second test database
DROP DATABASE test_db_bExpected Result:
- Success message or empty result
-- Verify second database was dropped
SHOW DATABASESExpected Result:
- Should NOT show
test_db_b - Should only show
nornic(default) andsystem
-- Verify default database still has original data
MATCH (n)
RETURN count(n) as node_countExpected Result:
- Should return the count of your original migrated data
- Confirms cleanup didn't affect default database
Test Summary
What This Test Verifies
- In-Place Upgrade: Existing data is accessible in default database after upgrade
- Database Creation: Can create multiple databases
- Data Isolation: Databases don't see each other's data
- Composite Databases: Can create virtual database spanning multiple databases
- Cross-Database Queries: Composite database provides unified view
- Schema Merging: Composite database merges schemas from constituents
- Write Routing: Writes to composite database are routed correctly
- Proper Cleanup: Can drop composite and constituent databases in correct order
Key Observations
- Isolation: Each database maintains complete data isolation
- Composite View: Composite database provides transparent access to all constituents
- No Data Loss: Dropping composite database doesn't affect constituent databases
- Default Database: Original migrated data remains intact throughout all operations
Important Notes
Database Switching:
- In Cypher Shell, use
:USE database_name - In drivers, specify
database="database_name"in connection/session - In HTTP API, use
/db/database_name/tx/commit
- In Cypher Shell, use
Cleanup Order:
- Always drop composite databases BEFORE dropping constituent databases
- Dropping a constituent database that's part of a composite will fail
Default Database:
- The default database (
nornic) is protected and cannot be dropped - The
systemdatabase is internal and cannot be dropped
- The default database (
Data Persistence:
- All data persists across server restarts
- Database metadata is stored in the
systemdatabase
Troubleshooting
Issue: "Database 'neo4j' not found"
Cause: Your client is trying to connect to "neo4j" (Neo4j's default), but NornicDB uses "nornic" as the default database name.
Solutions:
Configure NornicDB to use
"neo4j"as default (Recommended):Option A: Config File (
nornicdb.yaml):yamldatabase: default_database: "neo4j"Then restart NornicDB.
Option B: Environment Variable:
bashexport NORNICDB_DEFAULT_DATABASE=neo4j # Or Neo4j-compatible: export NEO4J_dbms_default__database=neo4jThen restart NornicDB.
Option C: Command Line:
bash./nornicdb serve --default-database neo4jSwitch to
"nornic"database in your client:In Web UI:
- Look for a database selector/dropdown
- Select
"nornic"from the list - Or manually change the URL from
/db/neo4j/to/db/nornic/
In Cypher Shell:
cypher:USE nornicIn Drivers:
python# Python driver = GraphDatabase.driver( "bolt://localhost:7687", database="nornic" # Use nornic instead of neo4j )Verify available databases:
cypherSHOW DATABASESThis will show you which databases exist. The default database will be marked with
default: true.
Issue: "database not found"
- Solution: Verify database exists with
SHOW DATABASES - Solution: Check you're using the correct database name (case-sensitive)
- Solution: If you see
"nornic"in the list but your client is looking for"neo4j", see the issue above
Issue: "cannot drop database: database is in use"
- Solution: Ensure no active connections are using the database
- Solution: Close all sessions/connections to the database
Issue: "cannot drop database: database is a constituent"
- Solution: Drop the composite database first, then drop the constituent
Issue: "composite database not found"
- Solution: Verify composite database exists with
SHOW COMPOSITE DATABASES - Solution: Check spelling and case sensitivity
Issue: Query returns unexpected results
- Solution: Verify you're connected to the correct database (
:USE database_name) - Solution: Check that data was created in the expected database
- Solution: Verify isolation by querying each database individually
- Solution: If using Web UI, check the database selector shows the correct database
Next Steps
After completing this test:
- Performance Testing: Test with larger datasets to verify performance
- Concurrent Access: Test multiple clients accessing different databases simultaneously
- Schema Operations: Test creating indexes/constraints in individual databases and verify merging
- Error Handling: Test error scenarios (dropping non-existent databases, etc.)
- Resource Limits: Test per-database resource limits if configured