Skip to content

Regression in 0.19.x: relationship tables in an attached database are unreachable, and USE no longer switches the default catalog #801

Description

@glier

Summary

Since 0.19.0 — and still in 0.19.1 — querying an attached database is broken in two ways that both worked in 0.18.3:

  1. USE <alias> no longer switches the default catalog. It still answers Used database successfully, but subsequent unqualified queries fail with Table <X> does not exist.
  2. Relationship tables in an attached database are unreachable. The qualified form is rejected as unimplemented, the unqualified form cannot find the table — and the anonymous form silently returns 0 rows instead of erroring.

That last one — the silent 0 — is the reason I'm filing this rather than working around it. A traversal over an attached database returns an empty result with no error, so an application reading a correct database gets a wrong answer that looks like a legitimate "nothing found". Node-only queries keep returning correct counts, so nothing signals that anything is wrong.

Reproduction

Self-contained, no extensions and no object storage — two local databases:

// repro.mjs — node repro.mjs
import lbug from '@ladybugdb/core';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

const root = mkdtempSync(join(tmpdir(), 'lbug-attach-'));
const target = join(root, 'target.lbdb');
const shell = join(root, 'shell.lbdb');

const q = async (conn, sql) => {
  const r = await conn.query(sql);
  return await (Array.isArray(r) ? r[r.length - 1] : r).getAll();
};
const n = (v) => (typeof v === 'bigint' ? Number(v) : v);

// Database to be attached: 3 nodes, 2 relationships.
{
  const db = new lbug.Database(target);
  const conn = new lbug.Connection(db);
  await q(conn, 'CREATE NODE TABLE Person(name STRING, PRIMARY KEY(name))');
  await q(conn, 'CREATE REL TABLE Knows(FROM Person TO Person)');
  await q(conn, "CREATE (:Person {name:'a'}), (:Person {name:'b'}), (:Person {name:'c'})");
  await q(conn, "MATCH (x:Person {name:'a'}), (y:Person {name:'b'}) CREATE (x)-[:Knows]->(y)");
  await q(conn, "MATCH (x:Person {name:'b'}), (y:Person {name:'c'}) CREATE (x)-[:Knows]->(y)");
  await q(conn, 'CHECKPOINT');
  await conn.close?.();
  await db.close?.();
}

const db = new lbug.Database(shell);
const conn = new lbug.Connection(db);
const t = async (label, sql) => {
  try {
    console.log(`  OK   ${label.padEnd(38)} ${JSON.stringify((await q(conn, sql))[0], (_k, v) => n(v))}`);
  } catch (e) {
    console.log(`  FAIL ${label.padEnd(38)} ${String(e).split('\n')[0]}`);
  }
};

console.log(`version: ${JSON.stringify((await q(conn, 'CALL db_version() RETURN *'))[0])}`);
await q(conn, `ATTACH '${target}' AS db1 (dbtype lbug)`);

await t('MATCH (p:db1.Person)', 'MATCH (p:db1.Person) RETURN count(p) AS n');
await t('-[e:db1.Knows]->', 'MATCH (a:db1.Person)-[e:db1.Knows]->(b:db1.Person) RETURN count(e) AS n');
await t('-[e:Knows]-> with db1.Person', 'MATCH (a:db1.Person)-[e:Knows]->(b:db1.Person) RETURN count(e) AS n');
await t('-[e]-> anonymous', 'MATCH (a:db1.Person)-[e]->(b:db1.Person) RETURN count(e) AS n');
await t('USE db1', 'USE db1');
await t('MATCH (p:Person)', 'MATCH (p:Person) RETURN count(p) AS n');
await t('-[e:Knows]->', 'MATCH (a:Person)-[e:Knows]->(b:Person) RETURN count(e) AS n');

await conn.close?.();
await db.close?.();
rmSync(root, { recursive: true, force: true });

Expected throughout: 3 nodes, 2 relationships.

Result

query 0.18.3 0.19.0
MATCH (p:db1.Person) 3 3
-[e:db1.Knows]-> Table db1.Knows does not exist Qualified relationship patterns (e.g. -[r:db.rel]->) are not supported yet
-[e:Knows]-> with db1.Person endpoints 2 Table Knows does not exist
-[e]-> anonymous 2 0 ← no error
USE db1 Used database successfully Used database successfully
MATCH (p:Person) after USE 3 Table Person does not exist
-[e:Knows]-> after USE 2 Table Person does not exist

So on 0.18.3 there are two working ways to reach an attached database — USE plus unqualified names, or qualified node labels with an unqualified relationship label. On 0.19.0 neither works, and one of them fails silently.

Impact

We use an attached database as a read-only view over a graph produced elsewhere. Node lookups still behave correctly on 0.19.0, so the failure is invisible until a traversal runs — and then it reports "no relationships" for a database that has them. For an application that answers questions like "what depends on this", an empty result is indistinguishable from a legitimate answer.

Pinning to 0.18.x is a workaround, but it is not one we can hold indefinitely.

Environment

status version platform
broken @ladybugdb/core 0.19.1 (CALL db_version()0.19.1) darwin/arm64, Node 22
broken @ladybugdb/core 0.19.0 (CALL db_version()0.19.0) linux/arm64, Node 22
working @ladybugdb/core 0.18.3 (CALL db_version()0.18.3) darwin/arm64, Node 22

0.19.0 and 0.19.1 produce byte-identical output on the reproduction above, on two different platforms. I have not tested the releases between 0.18.3 and 0.19.0, so the regression is bracketed as "somewhere after 0.18.3, present in both 0.19.0 and 0.19.1".

The same behaviour appears with a database attached over httpfs from object storage, which is how we hit it; the reproduction above avoids that path entirely to keep the report minimal.

Question

Is USE intended to keep working for attached databases, or is qualified addressing meant to become the only supported form? If it is the latter, -[r:alias.Rel]-> presumably needs to be implemented first — otherwise relationship tables in an attached database have no reachable syntax at all. And in either case, the anonymous pattern returning 0 rather than raising would be worth fixing on its own.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions