2
0

schema_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. package storage
  2. import (
  3. "bufio"
  4. "fmt"
  5. "net"
  6. "sort"
  7. "strings"
  8. "sync"
  9. "testing"
  10. "time"
  11. )
  12. type testScan struct {
  13. keys []string
  14. offset int
  15. limit uint32
  16. keysOnly bool
  17. }
  18. type testKVServer struct {
  19. mu sync.Mutex
  20. data map[string][]byte
  21. lsns map[string]uint64
  22. writes map[string]int
  23. nextLSN uint64
  24. maxScanPage uint32
  25. scanOpens int
  26. scanNexts int
  27. scanCloses int
  28. keyOnlyOpens int
  29. gets int
  30. multiGets int
  31. closers []net.Conn
  32. }
  33. func newTestKVServer(t *testing.T) *testKVServer {
  34. t.Helper()
  35. return &testKVServer{
  36. data: make(map[string][]byte),
  37. lsns: make(map[string]uint64),
  38. writes: make(map[string]int),
  39. }
  40. }
  41. func newTestKVPool(kv *testKVServer, size int, timeout time.Duration) *KVPool {
  42. pool := &KVPool{
  43. pool: make(chan *KVClient, size),
  44. size: size,
  45. timeout: timeout,
  46. }
  47. for i := 0; i < size; i++ {
  48. pool.pool <- kv.client()
  49. }
  50. return pool
  51. }
  52. func (s *testKVServer) close() {
  53. s.mu.Lock()
  54. closers := append([]net.Conn(nil), s.closers...)
  55. s.mu.Unlock()
  56. for _, conn := range closers {
  57. _ = conn.Close()
  58. }
  59. }
  60. func (s *testKVServer) client() *KVClient {
  61. clientConn, serverConn := net.Pipe()
  62. s.mu.Lock()
  63. s.closers = append(s.closers, clientConn, serverConn)
  64. s.mu.Unlock()
  65. go s.handle(serverConn)
  66. return &KVClient{
  67. conn: clientConn,
  68. reader: bufio.NewReader(clientConn),
  69. writer: bufio.NewWriter(clientConn),
  70. nextID: 1,
  71. lastUsed: time.Now(),
  72. }
  73. }
  74. func (s *testKVServer) writeCount(prefix string) int {
  75. s.mu.Lock()
  76. defer s.mu.Unlock()
  77. var count int
  78. for key, writes := range s.writes {
  79. if strings.Contains(key, prefix) {
  80. count += writes
  81. }
  82. }
  83. return count
  84. }
  85. func (s *testKVServer) hasKey(key string) bool {
  86. s.mu.Lock()
  87. defer s.mu.Unlock()
  88. _, ok := s.data[key]
  89. return ok
  90. }
  91. func (s *testKVServer) countKeys(prefix string) int {
  92. s.mu.Lock()
  93. defer s.mu.Unlock()
  94. var count int
  95. for key := range s.data {
  96. if strings.HasPrefix(key, prefix) {
  97. count++
  98. }
  99. }
  100. return count
  101. }
  102. func (s *testKVServer) scanStats() (opens, nexts, closes int) {
  103. s.mu.Lock()
  104. defer s.mu.Unlock()
  105. return s.scanOpens, s.scanNexts, s.scanCloses
  106. }
  107. func (s *testKVServer) keyOnlyOpenCount() int {
  108. s.mu.Lock()
  109. defer s.mu.Unlock()
  110. return s.keyOnlyOpens
  111. }
  112. func (s *testKVServer) readStats() (gets, multiGets int) {
  113. s.mu.Lock()
  114. defer s.mu.Unlock()
  115. return s.gets, s.multiGets
  116. }
  117. func (s *testKVServer) handle(conn net.Conn) {
  118. defer conn.Close()
  119. r := bufio.NewReader(conn)
  120. scans := make(map[uint64]*testScan)
  121. var nextScan uint64 = 1
  122. for {
  123. opcode, _, requestID, payload, err := readFrame(r)
  124. if err != nil {
  125. return
  126. }
  127. body := s.execute(opcode, payload, scans, &nextScan)
  128. if _, err := conn.Write(encodeResponse(opcode, requestID, body)); err != nil {
  129. return
  130. }
  131. }
  132. }
  133. func (s *testKVServer) execute(opcode uint16, payload []byte, scans map[uint64]*testScan, nextScan *uint64) []byte {
  134. switch opcode {
  135. case opPing:
  136. body := make([]byte, 2+len(payload))
  137. putU16(body[0:2], statusOK)
  138. copy(body[2:], payload)
  139. return body
  140. case opGet:
  141. key, ok := parseOneKey(payload)
  142. if !ok {
  143. return errorBody("InvalidPayload")
  144. }
  145. s.mu.Lock()
  146. s.gets++
  147. value, found := s.data[string(key)]
  148. lsn := s.lsns[string(key)]
  149. s.mu.Unlock()
  150. if !found {
  151. body := make([]byte, 2)
  152. putU16(body[0:2], statusNotFound)
  153. return body
  154. }
  155. body := make([]byte, 14+len(value))
  156. putU16(body[0:2], statusOK)
  157. putU64(body[2:10], lsn)
  158. putU32(body[10:14], uint32(len(value)))
  159. copy(body[14:], value)
  160. return body
  161. case opPut:
  162. key, value, ok := parsePut(payload)
  163. if !ok {
  164. return errorBody("InvalidPayload")
  165. }
  166. s.mu.Lock()
  167. s.nextLSN++
  168. lsn := s.nextLSN
  169. s.data[string(key)] = append([]byte(nil), value...)
  170. s.lsns[string(key)] = lsn
  171. s.writes[string(key)]++
  172. s.mu.Unlock()
  173. body := make([]byte, 10)
  174. putU16(body[0:2], statusOK)
  175. putU64(body[2:10], lsn)
  176. return body
  177. case opDelete:
  178. key, ok := parseOneKey(payload)
  179. if !ok {
  180. return errorBody("InvalidPayload")
  181. }
  182. s.mu.Lock()
  183. _, found := s.data[string(key)]
  184. delete(s.data, string(key))
  185. delete(s.lsns, string(key))
  186. s.mu.Unlock()
  187. body := make([]byte, 3)
  188. putU16(body[0:2], statusOK)
  189. if found {
  190. body[2] = 1
  191. }
  192. return body
  193. case opExists:
  194. key, ok := parseOneKey(payload)
  195. if !ok {
  196. return errorBody("InvalidPayload")
  197. }
  198. s.mu.Lock()
  199. _, found := s.data[string(key)]
  200. s.mu.Unlock()
  201. body := make([]byte, 3)
  202. putU16(body[0:2], statusOK)
  203. if found {
  204. body[2] = 1
  205. }
  206. return body
  207. case opMultiGet:
  208. keys, ok := parseMultiGetKeys(payload)
  209. if !ok {
  210. return errorBody("InvalidPayload")
  211. }
  212. body := make([]byte, 6)
  213. putU16(body[0:2], statusOK)
  214. putU32(body[2:6], uint32(len(keys)))
  215. s.mu.Lock()
  216. s.multiGets++
  217. for _, key := range keys {
  218. value, found := s.data[string(key)]
  219. if !found {
  220. body = append(body, make([]byte, 16)...)
  221. continue
  222. }
  223. lsn := s.lsns[string(key)]
  224. entry := make([]byte, 16+len(value))
  225. entry[0] = 1
  226. putU32(entry[4:8], uint32(len(value)))
  227. putU64(entry[8:16], lsn)
  228. copy(entry[16:], value)
  229. body = append(body, entry...)
  230. }
  231. s.mu.Unlock()
  232. return body
  233. case opBatchWrite:
  234. ops, ok := parseBatchOps(payload)
  235. if !ok {
  236. return errorBody("InvalidPayload")
  237. }
  238. s.mu.Lock()
  239. s.nextLSN++
  240. lsn := s.nextLSN
  241. for _, op := range ops {
  242. if op.Op == batchPut {
  243. s.data[string(op.Key)] = append([]byte(nil), op.Value...)
  244. s.lsns[string(op.Key)] = lsn
  245. s.writes[string(op.Key)]++
  246. } else {
  247. delete(s.data, string(op.Key))
  248. delete(s.lsns, string(op.Key))
  249. }
  250. }
  251. s.mu.Unlock()
  252. body := make([]byte, 10)
  253. putU16(body[0:2], statusOK)
  254. putU64(body[2:10], lsn)
  255. return body
  256. case opScanOpen:
  257. includeValues, limit, prefix, ok := parseScanOpen(payload)
  258. if !ok {
  259. return errorBody("InvalidPayload")
  260. }
  261. s.mu.Lock()
  262. s.scanOpens++
  263. if !includeValues {
  264. s.keyOnlyOpens++
  265. }
  266. s.mu.Unlock()
  267. s.mu.Lock()
  268. keys := make([]string, 0)
  269. for key := range s.data {
  270. if strings.HasPrefix(key, string(prefix)) {
  271. keys = append(keys, key)
  272. }
  273. }
  274. s.mu.Unlock()
  275. sort.Strings(keys)
  276. if s.maxScanPage > 0 && limit > s.maxScanPage {
  277. limit = s.maxScanPage
  278. }
  279. id := *nextScan
  280. *nextScan = id + 1
  281. scans[id] = &testScan{keys: keys, limit: limit, keysOnly: !includeValues}
  282. body := make([]byte, 10)
  283. putU16(body[0:2], statusOK)
  284. putU64(body[2:10], id)
  285. return body
  286. case opScanNext:
  287. id, ok := parseScanID(payload)
  288. if !ok {
  289. return errorBody("InvalidPayload")
  290. }
  291. scan := scans[id]
  292. if scan == nil {
  293. return errorBody("ScanNotFound")
  294. }
  295. s.mu.Lock()
  296. s.scanNexts++
  297. s.mu.Unlock()
  298. remaining := len(scan.keys) - scan.offset
  299. count := int(scan.limit)
  300. if count > remaining {
  301. count = remaining
  302. }
  303. end := scan.offset + count
  304. body := make([]byte, 10)
  305. putU16(body[0:2], statusOK)
  306. if end >= len(scan.keys) {
  307. body[2] = 1
  308. }
  309. putU32(body[6:10], uint32(count))
  310. s.mu.Lock()
  311. for _, key := range scan.keys[scan.offset:end] {
  312. value := s.data[key]
  313. if scan.keysOnly {
  314. value = nil
  315. }
  316. lsn := s.lsns[key]
  317. entry := make([]byte, 16+len(key)+len(value))
  318. putU32(entry[0:4], uint32(len(key)))
  319. putU32(entry[4:8], uint32(len(value)))
  320. putU64(entry[8:16], lsn)
  321. copy(entry[16:], key)
  322. copy(entry[16+len(key):], value)
  323. body = append(body, entry...)
  324. }
  325. s.mu.Unlock()
  326. scan.offset = end
  327. return body
  328. case opScanClose:
  329. id, ok := parseScanID(payload)
  330. if !ok {
  331. return errorBody("InvalidPayload")
  332. }
  333. s.mu.Lock()
  334. s.scanCloses++
  335. s.mu.Unlock()
  336. delete(scans, id)
  337. body := make([]byte, 3)
  338. putU16(body[0:2], statusOK)
  339. body[2] = 1
  340. return body
  341. default:
  342. return errorBody("UnknownOpcode")
  343. }
  344. }
  345. func parsePut(payload []byte) ([]byte, []byte, bool) {
  346. if len(payload) < 8 {
  347. return nil, nil, false
  348. }
  349. keyLen := getU32(payload[0:4])
  350. valueLen := getU32(payload[4:8])
  351. if keyLen > maxKeySize || valueLen > maxValueSize {
  352. return nil, nil, false
  353. }
  354. if uint64(8)+uint64(keyLen)+uint64(valueLen) != uint64(len(payload)) {
  355. return nil, nil, false
  356. }
  357. return payload[8 : 8+keyLen], payload[8+keyLen:], true
  358. }
  359. func parseMultiGetKeys(payload []byte) ([][]byte, bool) {
  360. if len(payload) < 4 {
  361. return nil, false
  362. }
  363. count := getU32(payload[0:4])
  364. if count > maxOperations {
  365. return nil, false
  366. }
  367. keys := make([][]byte, 0, count)
  368. pos := 4
  369. for i := uint32(0); i < count; i++ {
  370. if len(payload)-pos < 4 {
  371. return nil, false
  372. }
  373. length := getU32(payload[pos : pos+4])
  374. pos += 4
  375. if length > maxKeySize || len(payload)-pos < int(length) {
  376. return nil, false
  377. }
  378. keys = append(keys, payload[pos:pos+int(length)])
  379. pos += int(length)
  380. }
  381. return keys, pos == len(payload)
  382. }
  383. func parseBatchOps(payload []byte) ([]BatchOp, bool) {
  384. if len(payload) < 8 {
  385. return nil, false
  386. }
  387. count := getU32(payload[0:4])
  388. metadataLen := getU32(payload[4:8])
  389. if count == 0 || count > maxOperations || uint64(metadataLen) > uint64(len(payload)-8) {
  390. return nil, false
  391. }
  392. pos := 8 + int(metadataLen)
  393. ops := make([]BatchOp, 0, count)
  394. for i := uint32(0); i < count; i++ {
  395. if len(payload)-pos < 12 {
  396. return nil, false
  397. }
  398. opcode := payload[pos]
  399. keyLen := getU32(payload[pos+4 : pos+8])
  400. valueLen := getU32(payload[pos+8 : pos+12])
  401. pos += 12
  402. if opcode != batchPut && opcode != batchDelete {
  403. return nil, false
  404. }
  405. if keyLen > maxKeySize || valueLen > maxValueSize {
  406. return nil, false
  407. }
  408. if opcode == batchDelete && valueLen != 0 {
  409. return nil, false
  410. }
  411. if len(payload)-pos < int(keyLen)+int(valueLen) {
  412. return nil, false
  413. }
  414. key := payload[pos : pos+int(keyLen)]
  415. pos += int(keyLen)
  416. value := payload[pos : pos+int(valueLen)]
  417. pos += int(valueLen)
  418. ops = append(ops, BatchOp{Op: opcode, Key: key, Value: value})
  419. }
  420. return ops, pos == len(payload)
  421. }
  422. func parseScanOpen(payload []byte) (bool, uint32, []byte, bool) {
  423. if len(payload) < 12 {
  424. return false, 0, nil, false
  425. }
  426. includeValues := payload[0] != 0
  427. limit := getU32(payload[4:8])
  428. prefixLen := getU32(payload[8:12])
  429. if limit == 0 || limit > 4096 || prefixLen > maxKeySize {
  430. return false, 0, nil, false
  431. }
  432. if uint64(12)+uint64(prefixLen) != uint64(len(payload)) {
  433. return false, 0, nil, false
  434. }
  435. return includeValues, limit, payload[12:], true
  436. }
  437. func parseScanID(payload []byte) (uint64, bool) {
  438. if len(payload) < 8 {
  439. return 0, false
  440. }
  441. return getU64(payload[0:8]), true
  442. }
  443. func TestInsertDoesNotRewriteSchemaForRowIDUpdates(t *testing.T) {
  444. kv := newTestKVServer(t)
  445. defer kv.close()
  446. pool := newTestKVPool(kv, 2, 5*time.Second)
  447. defer pool.Close()
  448. schemas := NewSchemaManager(pool, "testdb")
  449. tables := NewTableManager(pool, schemas, "testdb")
  450. err := schemas.CreateTable(&Schema{
  451. Name: "users",
  452. Columns: []Column{
  453. {Name: "id", Type: "INTEGER", Nullable: false, PrimaryKey: true},
  454. {Name: "name", Type: "TEXT", Nullable: true},
  455. },
  456. })
  457. if err != nil {
  458. t.Fatalf("create table: %v", err)
  459. }
  460. initialSchemaWrites := kv.writeCount(":_schema:")
  461. if initialSchemaWrites != 1 {
  462. t.Fatalf("expected create table to write schema once, got %d", initialSchemaWrites)
  463. }
  464. for i := int64(1); i <= 3; i++ {
  465. err := tables.Insert("users", Row{"id": i, "name": fmt.Sprintf("user-%d", i)})
  466. if err != nil {
  467. t.Fatalf("insert %d: %v", i, err)
  468. }
  469. }
  470. if got := kv.writeCount(":_schema:"); got != initialSchemaWrites {
  471. t.Fatalf("expected inserts not to rewrite schema, got %d schema writes", got)
  472. }
  473. if got := kv.writeCount(":_sys:rowid:"); got != 0 {
  474. t.Fatalf("expected no rowid counter writes for inserts, got %d", got)
  475. }
  476. }
  477. func TestRowIDIsDerivedFromRowsAfterRestart(t *testing.T) {
  478. kv := newTestKVServer(t)
  479. defer kv.close()
  480. pool := newTestKVPool(kv, 2, 5*time.Second)
  481. defer pool.Close()
  482. schemas := NewSchemaManager(pool, "testdb")
  483. tables := NewTableManager(pool, schemas, "testdb")
  484. err := schemas.CreateTable(&Schema{
  485. Name: "events",
  486. Columns: []Column{
  487. {Name: "name", Type: "TEXT", Nullable: true},
  488. },
  489. })
  490. if err != nil {
  491. t.Fatalf("create table: %v", err)
  492. }
  493. for i := 1; i <= 2; i++ {
  494. err := tables.Insert("events", Row{"name": fmt.Sprintf("event-%d", i)})
  495. if err != nil {
  496. t.Fatalf("insert %d: %v", i, err)
  497. }
  498. }
  499. // Simulate a process restart: new managers have empty in-memory ROWID state
  500. // but the same durable KV rows.
  501. restartedSchemas := NewSchemaManager(pool, "testdb")
  502. restartedTables := NewTableManager(pool, restartedSchemas, "testdb")
  503. if err := restartedTables.Insert("events", Row{"name": "event-3"}); err != nil {
  504. t.Fatalf("insert after restart: %v", err)
  505. }
  506. if !kv.hasKey("testdb:_data:events:3") {
  507. t.Fatalf("expected restart insert to continue at rowid 3")
  508. }
  509. if got := kv.writeCount(":_sys:rowid:"); got != 0 {
  510. t.Fatalf("expected no rowid counter writes, got %d", got)
  511. }
  512. }
  513. func TestInsertDoesNotWriteDurableIndexEntries(t *testing.T) {
  514. kv := newTestKVServer(t)
  515. defer kv.close()
  516. pool := newTestKVPool(kv, 2, 5*time.Second)
  517. defer pool.Close()
  518. schemas := NewSchemaManager(pool, "testdb")
  519. tables := NewTableManager(pool, schemas, "testdb")
  520. err := schemas.CreateTable(&Schema{
  521. Name: "users",
  522. Columns: []Column{
  523. {Name: "id", Type: "INTEGER", Nullable: false, PrimaryKey: true},
  524. {Name: "status", Type: "TEXT", Nullable: false},
  525. },
  526. })
  527. if err != nil {
  528. t.Fatalf("create table: %v", err)
  529. }
  530. err = schemas.CreateIndex(&Index{
  531. Name: "idx_users_status",
  532. Table: "users",
  533. Columns: []IndexColumn{
  534. {Name: "status"},
  535. },
  536. })
  537. if err != nil {
  538. t.Fatalf("create index: %v", err)
  539. }
  540. for i := int64(1); i <= 3; i++ {
  541. status := "active"
  542. if i == 2 {
  543. status = "inactive"
  544. }
  545. err := tables.Insert("users", Row{"id": i, "status": status})
  546. if err != nil {
  547. t.Fatalf("insert %d: %v", i, err)
  548. }
  549. }
  550. if got := kv.writeCount(":idx:"); got != 0 {
  551. t.Fatalf("expected no durable index entry writes, got %d", got)
  552. }
  553. rows, err := tables.SelectByIndex("users", "idx_users_status", "active")
  554. if err != nil {
  555. t.Fatalf("select by index: %v", err)
  556. }
  557. if len(rows) != 2 {
  558. t.Fatalf("expected 2 active rows from derived index, got %d", len(rows))
  559. }
  560. }
  561. func TestIndexIsDerivedFromRowsAfterRestart(t *testing.T) {
  562. kv := newTestKVServer(t)
  563. defer kv.close()
  564. pool := newTestKVPool(kv, 2, 5*time.Second)
  565. defer pool.Close()
  566. schemas := NewSchemaManager(pool, "testdb")
  567. tables := NewTableManager(pool, schemas, "testdb")
  568. err := schemas.CreateTable(&Schema{
  569. Name: "users",
  570. Columns: []Column{
  571. {Name: "id", Type: "INTEGER", Nullable: false, PrimaryKey: true},
  572. {Name: "status", Type: "TEXT", Nullable: false},
  573. },
  574. })
  575. if err != nil {
  576. t.Fatalf("create table: %v", err)
  577. }
  578. err = schemas.CreateIndex(&Index{
  579. Name: "idx_users_status",
  580. Table: "users",
  581. Columns: []IndexColumn{
  582. {Name: "status"},
  583. },
  584. })
  585. if err != nil {
  586. t.Fatalf("create index: %v", err)
  587. }
  588. for i := int64(1); i <= 3; i++ {
  589. status := "active"
  590. if i == 3 {
  591. status = "inactive"
  592. }
  593. if err := tables.Insert("users", Row{"id": i, "status": status}); err != nil {
  594. t.Fatalf("insert %d: %v", i, err)
  595. }
  596. }
  597. restartedSchemas := NewSchemaManager(pool, "testdb")
  598. restartedTables := NewTableManager(pool, restartedSchemas, "testdb")
  599. rows, err := restartedTables.SelectByIndex("users", "idx_users_status", "active")
  600. if err != nil {
  601. t.Fatalf("select by index after restart: %v", err)
  602. }
  603. if len(rows) != 2 {
  604. t.Fatalf("expected 2 active rows from restart-derived index, got %d", len(rows))
  605. }
  606. if got := kv.writeCount(":idx:"); got != 0 {
  607. t.Fatalf("expected no durable index entry writes, got %d", got)
  608. }
  609. }
  610. func TestListTableIndexesCachesMetadata(t *testing.T) {
  611. kv := newTestKVServer(t)
  612. defer kv.close()
  613. pool := newTestKVPool(kv, 2, 5*time.Second)
  614. defer pool.Close()
  615. schemas := NewSchemaManager(pool, "testdb")
  616. if err := schemas.CreateTable(&Schema{
  617. Name: "items",
  618. Columns: []Column{
  619. {Name: "id", Type: "INTEGER", PrimaryKey: true},
  620. {Name: "kind", Type: "TEXT"},
  621. },
  622. }); err != nil {
  623. t.Fatal(err)
  624. }
  625. if err := schemas.CreateIndex(&Index{
  626. Name: "idx_items_kind", Table: "items", Columns: []IndexColumn{{Name: "kind"}},
  627. }); err != nil {
  628. t.Fatal(err)
  629. }
  630. restarted := NewSchemaManager(pool, "testdb")
  631. getsBefore, _ := kv.readStats()
  632. if indexes, err := restarted.ListTableIndexes("items"); err != nil || len(indexes) != 1 {
  633. t.Fatalf("first list: indexes=%v err=%v", indexes, err)
  634. }
  635. getsAfterFirst, _ := kv.readStats()
  636. if getsAfterFirst <= getsBefore {
  637. t.Fatal("first index metadata lookup did not read durable metadata")
  638. }
  639. for i := 0; i < 10; i++ {
  640. if indexes, err := restarted.ListTableIndexes("items"); err != nil || len(indexes) != 1 {
  641. t.Fatalf("cached list %d: indexes=%v err=%v", i, indexes, err)
  642. }
  643. }
  644. getsAfterCached, _ := kv.readStats()
  645. if getsAfterCached != getsAfterFirst {
  646. t.Fatalf("cached index metadata issued %d extra reads", getsAfterCached-getsAfterFirst)
  647. }
  648. }