Skip to content

[BUG] Filtering with where returns wrong results #2814

@mindreframer

Description

@mindreframer

For simplicity I will use bun.js.

helpers file: https://github.com/mindreframer/arcadex/blob/main/jstester/tests/helpers.js

import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import {
  createDatabase,
  cleanupDatabase,
  uniqueDbName,
  command,
  query
} from './helpers.js';

describe('ULTRA MINIMAL ArcadeDB Bug - Plain SQL Only', () => {
  const dbName = uniqueDbName('ultra_minimal');

  beforeAll(async () => {
    await cleanupDatabase(dbName);
    await createDatabase(dbName);
  });

  afterAll(async () => {
    await cleanupDatabase(dbName);
  });

  test('absolute minimal reproduction', async () => {
    console.log('\n=== SETUP ===');

    // 1. Create two types with LINK and COMPOSITE INDEX 
    await command(dbName, `CREATE DOCUMENT TYPE Parent`);
    await command(dbName, `CREATE DOCUMENT TYPE Child`);
    await command(dbName, `CREATE PROPERTY Child.uid STRING`);
    await command(dbName, `CREATE PROPERTY Child.status STRING (default 'synced')`);
    await command(dbName, `CREATE PROPERTY Child.version INTEGER (default 1)`);
    await command(dbName, `CREATE PROPERTY Child.parent LINK Parent`);

    // THE KEY: Composite index on (status, version) 
    await command(dbName, `CREATE INDEX ON Child (status, version) NOTUNIQUE`);

    // 2. Create parent
    const p = await command(dbName, `INSERT INTO Parent SET name = 'p1' RETURN @this`);
    const pRid = p.result[0]['@rid'];

    // 3. Insert 3 children WITHOUT explicit status (use default)
    await command(dbName, `INSERT INTO Child SET uid = 'c1', parent = ${pRid}`);
    await command(dbName, `INSERT INTO Child SET uid = 'c2', parent = ${pRid}`);
    await command(dbName, `INSERT INTO Child SET uid = 'c3', parent = ${pRid}`);

    console.log('Created 3 children with default status=synced');

    // 4. Mark c1 and c2 as pending
    await command(dbName, `UPDATE Child SET status = 'pending' WHERE uid = 'c1'`);
    await command(dbName, `UPDATE Child SET status = 'pending' WHERE uid = 'c2'`);

    console.log('\n=== BEFORE BUG ===');

    // 5. Verify WHERE works - should find 2 pending
    const before = await query(dbName, `SELECT uid, status FROM Child WHERE status = 'pending'`);
    console.log('Pending (WHERE):', before.result.length, '→', before.result.map(r => r.uid));
    expect(before.result.length).toBe(2);

    console.log('\n=== TRIGGER BUG ===');

    // 6. Update c1 with parameterized multi-field UPDATE (including version field in composite index)
    await command(dbName, `UPDATE Child SET version = :version, status = :status WHERE uid = :uid`, {
      uid: 'c1',
      version: 2,
      status: 'synced'
    });

    console.log('Updated c1 to synced (multi-field parameterized UPDATE)');

    console.log('\n=== AFTER BUG ===');

    // 7. Check without WHERE - should show c2 pending, c1+c3 synced
    const all = await query(dbName, `SELECT uid, status FROM Child ORDER BY uid`);
    console.log('All (no WHERE):', all.result);

    // 8. BUG: WHERE status='pending' should find c2 but finds 0
    const pending = await query(dbName, `SELECT uid, status FROM Child WHERE status = 'pending'`);
    console.log('Pending (WHERE):', pending.result.length, '→', pending.result.map(r => r.uid));

    // 9. BUG: WHERE status='synced' should find c1+c3 but finds only c1
    const synced = await query(dbName, `SELECT uid, status FROM Child WHERE status = 'synced'`);
    console.log('Synced (WHERE):', synced.result.length, '→', synced.result.map(r => r.uid));

    console.log('\n=== RESULT ===');
    console.log(`Pending: expected 1 (c2), got ${pending.result.length}`);
    console.log(`Synced: expected 2 (c1,c3), got ${synced.result.length}`);

    expect(pending.result.length).toBe(1);
    expect(synced.result.length).toBe(2);
  });
});

=== SETUP ===
CREATE DOCUMENT TYPE Parent;
CREATE DOCUMENT TYPE Child;
CREATE PROPERTY Child.uid STRING;
CREATE PROPERTY Child.status STRING (default 'synced');
CREATE PROPERTY Child.version INTEGER (default 1);
CREATE PROPERTY Child.parent LINK Parent;
CREATE INDEX ON Child (status, version) NOTUNIQUE;
INSERT INTO Parent SET name = 'p1' RETURN @this;
INSERT INTO Child SET uid = 'c1', parent = #1:0;
INSERT INTO Child SET uid = 'c2', parent = #1:0;
INSERT INTO Child SET uid = 'c3', parent = #1:0;
Created 3 children with default status=synced
UPDATE Child SET status = 'pending' WHERE uid = 'c1';
UPDATE Child SET status = 'pending' WHERE uid = 'c2';

=== BEFORE BUG ===
SELECT uid, status FROM Child WHERE status = 'pending';
Pending (WHERE): 2 → [ "c1", "c2" ]

=== TRIGGER BUG ===
UPDATE Child SET version = :version, status = :status WHERE uid = :uid;
Updated c1 to synced (multi-field parameterized UPDATE)

=== AFTER BUG ===
SELECT uid, status FROM Child ORDER BY uid;
All (no WHERE): [
  {
    uid: "c1",
    status: "synced",
  }, {
    uid: "c2",
    status: "pending",
  }, {
    uid: "c3",
    status: "synced",
  }
]
SELECT uid, status FROM Child WHERE status = 'pending';
Pending (WHERE): 0 → []
SELECT uid, status FROM Child WHERE status = 'synced';
Synced (WHERE): 1 → [ "c1" ]

=== RESULT ===
Pending: expected 1 (c2), got 0
Synced: expected 2 (c1,c3), got 1

I suspect this has something to do with the parameterized multi-field UPDATE .

Good thing: it's consistent and quite reproducible.

Cheers!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions