@@ -537,3 +537,117 @@ def test_wal_concurrent_reader_during_write(tmp_path):
537537
538538 writer .close ()
539539 reader .close ()
540+
541+
542+ def test_insert_or_replace_integer_pk (db ):
543+ """INSERT OR REPLACE should update vector when rowid already exists."""
544+ db .execute ("create virtual table v using vec0(emb float[4], chunk_size=8)" )
545+
546+ db .execute (
547+ "insert into v(rowid, emb) values (1, ?)" , [_f32 ([1.0 , 2.0 , 3.0 , 4.0 ])]
548+ )
549+ # Replace with new vector
550+ db .execute (
551+ "insert or replace into v(rowid, emb) values (1, ?)" ,
552+ [_f32 ([10.0 , 20.0 , 30.0 , 40.0 ])],
553+ )
554+
555+ # Should still have exactly 1 row
556+ count = db .execute ("select count(*) from v" ).fetchone ()[0 ]
557+ assert count == 1
558+
559+ # Vector should be the replaced value
560+ row = db .execute ("select emb from v where rowid = 1" ).fetchone ()
561+ assert row [0 ] == _f32 ([10.0 , 20.0 , 30.0 , 40.0 ])
562+
563+
564+ def test_insert_or_replace_new_row (db ):
565+ """INSERT OR REPLACE with a new rowid should just insert normally."""
566+ db .execute ("create virtual table v using vec0(emb float[4], chunk_size=8)" )
567+
568+ db .execute (
569+ "insert or replace into v(rowid, emb) values (1, ?)" ,
570+ [_f32 ([1.0 , 2.0 , 3.0 , 4.0 ])],
571+ )
572+
573+ count = db .execute ("select count(*) from v" ).fetchone ()[0 ]
574+ assert count == 1
575+
576+ row = db .execute ("select emb from v where rowid = 1" ).fetchone ()
577+ assert row [0 ] == _f32 ([1.0 , 2.0 , 3.0 , 4.0 ])
578+
579+
580+ def test_insert_or_replace_text_pk (db ):
581+ """INSERT OR REPLACE should work with text primary keys."""
582+ db .execute (
583+ "create virtual table v using vec0("
584+ "id text primary key, emb float[4], chunk_size=8"
585+ ")"
586+ )
587+
588+ db .execute (
589+ "insert into v(id, emb) values ('doc_a', ?)" ,
590+ [_f32 ([1.0 , 2.0 , 3.0 , 4.0 ])],
591+ )
592+ db .execute (
593+ "insert or replace into v(id, emb) values ('doc_a', ?)" ,
594+ [_f32 ([10.0 , 20.0 , 30.0 , 40.0 ])],
595+ )
596+
597+ count = db .execute ("select count(*) from v" ).fetchone ()[0 ]
598+ assert count == 1
599+
600+ row = db .execute ("select emb from v where id = 'doc_a'" ).fetchone ()
601+ assert row [0 ] == _f32 ([10.0 , 20.0 , 30.0 , 40.0 ])
602+
603+
604+ def test_insert_or_replace_with_auxiliary (db ):
605+ """INSERT OR REPLACE should also replace auxiliary column values."""
606+ db .execute (
607+ "create virtual table v using vec0("
608+ "emb float[4], +label text, chunk_size=8"
609+ ")"
610+ )
611+
612+ db .execute (
613+ "insert into v(rowid, emb, label) values (1, ?, 'old')" ,
614+ [_f32 ([1.0 , 2.0 , 3.0 , 4.0 ])],
615+ )
616+ db .execute (
617+ "insert or replace into v(rowid, emb, label) values (1, ?, 'new')" ,
618+ [_f32 ([10.0 , 20.0 , 30.0 , 40.0 ])],
619+ )
620+
621+ count = db .execute ("select count(*) from v" ).fetchone ()[0 ]
622+ assert count == 1
623+
624+ row = db .execute ("select emb, label from v where rowid = 1" ).fetchone ()
625+ assert row [0 ] == _f32 ([10.0 , 20.0 , 30.0 , 40.0 ])
626+ assert row [1 ] == "new"
627+
628+
629+ def test_insert_or_replace_knn_uses_new_vector (db ):
630+ """After INSERT OR REPLACE, KNN should find the new vector, not the old one."""
631+ db .execute ("create virtual table v using vec0(emb float[4], chunk_size=8)" )
632+
633+ db .execute (
634+ "insert into v(rowid, emb) values (1, ?)" , [_f32 ([1.0 , 0.0 , 0.0 , 0.0 ])]
635+ )
636+ db .execute (
637+ "insert into v(rowid, emb) values (2, ?)" , [_f32 ([0.0 , 1.0 , 0.0 , 0.0 ])]
638+ )
639+
640+ # Replace row 1's vector to be very close to row 2
641+ db .execute (
642+ "insert or replace into v(rowid, emb) values (1, ?)" ,
643+ [_f32 ([0.0 , 0.9 , 0.0 , 0.0 ])],
644+ )
645+
646+ # KNN for [0, 1, 0, 0] should return row 2 first (exact), then row 1 (close)
647+ rows = db .execute (
648+ "select rowid, distance from v where emb match ? and k = 2" ,
649+ [_f32 ([0.0 , 1.0 , 0.0 , 0.0 ])],
650+ ).fetchall ()
651+ assert rows [0 ][0 ] == 2
652+ assert rows [1 ][0 ] == 1
653+ assert rows [1 ][1 ] < 0.11 # should be close (L2 distance ≈ 0.1)
0 commit comments