|
| 1 | +import sqlite3 |
| 2 | +import struct |
| 3 | +from collections import OrderedDict |
| 4 | +import pytest |
| 5 | + |
| 6 | + |
| 7 | +def _f32(list): |
| 8 | + return struct.pack("%sf" % len(list), *list) |
| 9 | + |
| 10 | + |
| 11 | +def exec(db, sql, parameters=[]): |
| 12 | + try: |
| 13 | + rows = db.execute(sql, parameters).fetchall() |
| 14 | + except (sqlite3.OperationalError, sqlite3.DatabaseError) as e: |
| 15 | + return { |
| 16 | + "error": e.__class__.__name__, |
| 17 | + "message": str(e), |
| 18 | + } |
| 19 | + a = [] |
| 20 | + for row in rows: |
| 21 | + o = OrderedDict() |
| 22 | + for k in row.keys(): |
| 23 | + o[k] = row[k] |
| 24 | + a.append(o) |
| 25 | + result = OrderedDict() |
| 26 | + result["sql"] = sql |
| 27 | + result["rows"] = a |
| 28 | + return result |
| 29 | + |
| 30 | + |
| 31 | +def test_insert_creates_chunks_and_vectors(db, snapshot): |
| 32 | + db.execute("create virtual table v using vec0(emb float[4], chunk_size=8)") |
| 33 | + |
| 34 | + vecs = [ |
| 35 | + [1.0, 2.0, 3.0, 4.0], |
| 36 | + [5.0, 6.0, 7.0, 8.0], |
| 37 | + [0.1, 0.2, 0.3, 0.4], |
| 38 | + [10.0, 20.0, 30.0, 40.0], |
| 39 | + [0.5, 0.5, 0.5, 0.5], |
| 40 | + ] |
| 41 | + for i, v in enumerate(vecs, start=1): |
| 42 | + db.execute("insert into v(rowid, emb) values (?, ?)", [i, _f32(v)]) |
| 43 | + |
| 44 | + assert exec(db, "select count(*) as cnt from v_rowids") == snapshot( |
| 45 | + name="rowids_count" |
| 46 | + ) |
| 47 | + assert exec(db, "select count(*) as cnt from v_vector_chunks00") == snapshot( |
| 48 | + name="vector_chunks_count" |
| 49 | + ) |
| 50 | + |
| 51 | + # Verify round-trip: each inserted vector comes back identical |
| 52 | + for i, v in enumerate(vecs, start=1): |
| 53 | + rows = db.execute("select emb from v where rowid = ?", [i]).fetchall() |
| 54 | + assert len(rows) == 1 |
| 55 | + assert rows[0][0] == _f32(v) |
| 56 | + |
| 57 | + |
| 58 | +def test_insert_auto_rowid(db): |
| 59 | + db.execute("create virtual table v using vec0(emb float[4], chunk_size=8)") |
| 60 | + |
| 61 | + vecs = [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]] |
| 62 | + for v in vecs: |
| 63 | + db.execute("insert into v(emb) values (?)", [_f32(v)]) |
| 64 | + |
| 65 | + rows = db.execute("select rowid from v order by rowid").fetchall() |
| 66 | + rowids = [r[0] for r in rows] |
| 67 | + assert rowids == [1, 2, 3] |
| 68 | + |
| 69 | + for i, v in enumerate(vecs, start=1): |
| 70 | + row = db.execute("select emb from v where rowid = ?", [i]).fetchone() |
| 71 | + assert row[0] == _f32(v) |
| 72 | + |
| 73 | + |
| 74 | +def test_insert_text_primary_key(db, snapshot): |
| 75 | + db.execute( |
| 76 | + "create virtual table v using vec0(id text primary key, emb float[4], chunk_size=8)" |
| 77 | + ) |
| 78 | + |
| 79 | + db.execute( |
| 80 | + "insert into v(id, emb) values ('doc_a', ?)", [_f32([1.0, 2.0, 3.0, 4.0])] |
| 81 | + ) |
| 82 | + db.execute( |
| 83 | + "insert into v(id, emb) values ('doc_b', ?)", [_f32([5.0, 6.0, 7.0, 8.0])] |
| 84 | + ) |
| 85 | + |
| 86 | + assert exec(db, "select rowid, id, chunk_id, chunk_offset from v_rowids order by rowid") == snapshot( |
| 87 | + name="rowids" |
| 88 | + ) |
| 89 | + |
| 90 | + row = db.execute("select emb from v where id = 'doc_a'").fetchone() |
| 91 | + assert row[0] == _f32([1.0, 2.0, 3.0, 4.0]) |
| 92 | + |
| 93 | + row = db.execute("select emb from v where id = 'doc_b'").fetchone() |
| 94 | + assert row[0] == _f32([5.0, 6.0, 7.0, 8.0]) |
| 95 | + |
| 96 | + |
| 97 | +def test_delete_clears_validity(db): |
| 98 | + db.execute("create virtual table v using vec0(emb float[4], chunk_size=8)") |
| 99 | + |
| 100 | + for i, v in enumerate( |
| 101 | + [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]], |
| 102 | + start=1, |
| 103 | + ): |
| 104 | + db.execute("insert into v(rowid, emb) values (?, ?)", [i, _f32(v)]) |
| 105 | + |
| 106 | + db.execute("delete from v where rowid = 2") |
| 107 | + |
| 108 | + rows = db.execute("select rowid from v order by rowid").fetchall() |
| 109 | + assert [r[0] for r in rows] == [1, 3] |
| 110 | + |
| 111 | + rowid_rows = db.execute("select rowid from v_rowids order by rowid").fetchall() |
| 112 | + assert 2 not in [r[0] for r in rowid_rows] |
| 113 | + |
| 114 | + # Inserting a new vector after deletion still works |
| 115 | + db.execute("insert into v(rowid, emb) values (4, ?)", [_f32([0.0, 0.0, 0.0, 1.0])]) |
| 116 | + row = db.execute("select emb from v where rowid = 4").fetchone() |
| 117 | + assert row[0] == _f32([0.0, 0.0, 0.0, 1.0]) |
| 118 | + |
| 119 | + |
| 120 | +def test_insert_delete_reinsert(db): |
| 121 | + db.execute("create virtual table v using vec0(emb float[4], chunk_size=8)") |
| 122 | + |
| 123 | + db.execute("insert into v(rowid, emb) values (1, ?)", [_f32([1.0, 1.0, 1.0, 1.0])]) |
| 124 | + db.execute("delete from v where rowid = 1") |
| 125 | + db.execute("insert into v(rowid, emb) values (2, ?)", [_f32([2.0, 2.0, 2.0, 2.0])]) |
| 126 | + |
| 127 | + rows = db.execute("select rowid from v order by rowid").fetchall() |
| 128 | + assert [r[0] for r in rows] == [2] |
| 129 | + |
| 130 | + # KNN query works and returns rowid 2 |
| 131 | + knn = db.execute( |
| 132 | + "select rowid, distance from v where emb match ? and k = 1", |
| 133 | + [_f32([2.0, 2.0, 2.0, 2.0])], |
| 134 | + ).fetchall() |
| 135 | + assert len(knn) == 1 |
| 136 | + assert knn[0][0] == 2 |
| 137 | + |
| 138 | + |
| 139 | +def test_insert_validates_dimensions(db): |
| 140 | + db.execute("create virtual table v using vec0(emb float[4], chunk_size=8)") |
| 141 | + |
| 142 | + result = exec(db, "insert into v(rowid, emb) values (1, ?)", [_f32([1.0, 2.0, 3.0])]) |
| 143 | + assert result["error"] == "OperationalError" |
| 144 | + assert "Dimension mismatch" in result["message"] |
| 145 | + assert "Expected 4" in result["message"] |
| 146 | + assert "3" in result["message"] |
| 147 | + |
| 148 | + result = exec( |
| 149 | + db, "insert into v(rowid, emb) values (1, ?)", [_f32([1.0, 2.0, 3.0, 4.0, 5.0])] |
| 150 | + ) |
| 151 | + assert result["error"] == "OperationalError" |
| 152 | + assert "Dimension mismatch" in result["message"] |
| 153 | + assert "Expected 4" in result["message"] |
| 154 | + assert "5" in result["message"] |
| 155 | + |
| 156 | + |
| 157 | +def test_insert_validates_type(db): |
| 158 | + db.execute("create virtual table v using vec0(emb float[4], chunk_size=8)") |
| 159 | + |
| 160 | + int8_vec = struct.pack("4b", 1, 2, 3, 4) |
| 161 | + result = exec( |
| 162 | + db, |
| 163 | + "insert into v(rowid, emb) values (1, vec_int8(?))", |
| 164 | + [int8_vec], |
| 165 | + ) |
| 166 | + assert "error" in result |
| 167 | + assert "float32" in result["message"] |
| 168 | + assert "int8" in result["message"] |
| 169 | + |
| 170 | + |
| 171 | +def test_info_table_contents(db, snapshot): |
| 172 | + db.execute("create virtual table v using vec0(emb float[4], chunk_size=8)") |
| 173 | + assert exec(db, "select key, value from v_info order by key") == snapshot() |
0 commit comments