schema.go 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  1. package storage
  2. import (
  3. "fmt"
  4. "strings"
  5. "sync"
  6. "time"
  7. "github.com/goccy/go-json"
  8. "github.com/danfragoso/pizzasql-next/pkg/analyzer"
  9. )
  10. // Schema represents a table schema.
  11. type Schema struct {
  12. Name string `json:"name"`
  13. Columns []Column `json:"columns"`
  14. PrimaryKey string `json:"primary_key"`
  15. CreatedAt time.Time `json:"created_at"`
  16. NextRowID int64 `json:"next_rowid"`
  17. AutoIncrement bool `json:"autoincrement"`
  18. }
  19. // Column represents a column definition.
  20. type Column struct {
  21. Name string `json:"name"`
  22. Type string `json:"type"`
  23. Nullable bool `json:"nullable"`
  24. Default interface{} `json:"default,omitempty"`
  25. PrimaryKey bool `json:"primary_key"`
  26. }
  27. // Index represents an index definition.
  28. type Index struct {
  29. Name string `json:"name"`
  30. Table string `json:"table"`
  31. Columns []IndexColumn `json:"columns"`
  32. Unique bool `json:"unique"`
  33. CreatedAt time.Time `json:"created_at"`
  34. }
  35. // IndexColumn represents a column in an index.
  36. type IndexColumn struct {
  37. Name string `json:"name"`
  38. Desc bool `json:"desc"`
  39. }
  40. // SchemaManager manages table schemas.
  41. type SchemaManager struct {
  42. pool *KVPool
  43. database string
  44. cache map[string]*Schema
  45. indexCache map[string]*Index
  46. indexListCache []string
  47. indexListCached bool
  48. rowIDInitialized map[string]bool
  49. version uint64
  50. mu sync.RWMutex
  51. txMu sync.RWMutex
  52. tableLocksMu sync.Mutex
  53. tableLocks map[string]*sync.RWMutex
  54. }
  55. // BeginTransaction prevents other connections from observing intermediate
  56. // changes until this connection commits or rolls back.
  57. func (m *SchemaManager) BeginTransaction() { m.txMu.Lock() }
  58. // EndTransaction releases the database transaction lock.
  59. func (m *SchemaManager) EndTransaction() { m.txMu.Unlock() }
  60. // LockStatement serializes a non-transactional statement with transactions.
  61. func (m *SchemaManager) LockStatement() { m.txMu.RLock() }
  62. // UnlockStatement releases a non-transactional statement lock.
  63. func (m *SchemaManager) UnlockStatement() { m.txMu.RUnlock() }
  64. // NewSchemaManager creates a new schema manager.
  65. func NewSchemaManager(pool *KVPool, database string) *SchemaManager {
  66. return &SchemaManager{
  67. pool: pool,
  68. database: database,
  69. cache: make(map[string]*Schema),
  70. indexCache: make(map[string]*Index),
  71. rowIDInitialized: make(map[string]bool),
  72. tableLocks: make(map[string]*sync.RWMutex),
  73. }
  74. }
  75. func (m *SchemaManager) tableLock(table string) *sync.RWMutex {
  76. key := strings.ToLower(table)
  77. m.tableLocksMu.Lock()
  78. lock, ok := m.tableLocks[key]
  79. if !ok {
  80. lock = &sync.RWMutex{}
  81. m.tableLocks[key] = lock
  82. }
  83. m.tableLocksMu.Unlock()
  84. return lock
  85. }
  86. // GetDatabaseName returns the database name.
  87. func (m *SchemaManager) GetDatabaseName() string {
  88. return m.database
  89. }
  90. // GetPool returns the KV pool.
  91. func (m *SchemaManager) GetPool() *KVPool {
  92. return m.pool
  93. }
  94. // Version returns the in-process schema catalog version. It is incremented for
  95. // schema/index definition changes so cached executors can resync their analyzer
  96. // catalogs without scanning storage on every query.
  97. func (m *SchemaManager) Version() uint64 {
  98. m.mu.RLock()
  99. defer m.mu.RUnlock()
  100. return m.version
  101. }
  102. func (m *SchemaManager) bumpVersionLocked() {
  103. m.version++
  104. }
  105. // schemaKey returns the key for a table schema.
  106. func (m *SchemaManager) schemaKey(table string) string {
  107. return fmt.Sprintf("%s:_schema:%s", m.database, strings.ToLower(table))
  108. }
  109. // catalogKey returns the key for the table catalog.
  110. func (m *SchemaManager) catalogKey() string {
  111. return fmt.Sprintf("%s:_sys:tables", m.database)
  112. }
  113. // rowIDKey returns the key for a table's next ROWID counter.
  114. func (m *SchemaManager) rowIDKey(table string) string {
  115. return fmt.Sprintf("%s:_sys:rowid:%s", m.database, strings.ToLower(table))
  116. }
  117. // CreateTable creates a new table.
  118. func (m *SchemaManager) CreateTable(schema *Schema) error {
  119. m.mu.Lock()
  120. defer m.mu.Unlock()
  121. // Check if table already exists
  122. key := m.schemaKey(schema.Name)
  123. err := m.pool.WithClient(func(c *KVClient) error {
  124. _, err := c.Read(key)
  125. return err
  126. })
  127. if err == nil {
  128. return fmt.Errorf("table already exists: %s", schema.Name)
  129. }
  130. // Keep the cached schema private so callers cannot mutate a published
  131. // catalog snapshot after this operation returns.
  132. schema = cloneSchema(schema)
  133. schema.CreatedAt = time.Now()
  134. // Determine primary key if not set
  135. if schema.PrimaryKey == "" {
  136. for _, col := range schema.Columns {
  137. if col.PrimaryKey {
  138. schema.PrimaryKey = col.Name
  139. break
  140. }
  141. }
  142. // No explicit primary key declared — use synthetic _rowid_ so user
  143. // columns remain unconstrained and can hold duplicate or NULL values.
  144. if schema.PrimaryKey == "" {
  145. schema.PrimaryKey = "_rowid_"
  146. }
  147. }
  148. // Serialize schema
  149. data, err := json.Marshal(schema)
  150. if err != nil {
  151. return fmt.Errorf("failed to serialize schema: %w", err)
  152. }
  153. // Write schema
  154. err = m.pool.WithClient(func(c *KVClient) error {
  155. return c.Write(key, string(data))
  156. })
  157. if err != nil {
  158. return fmt.Errorf("failed to write schema: %w", err)
  159. }
  160. // Update catalog
  161. if err := m.addToCatalog(schema.Name); err != nil {
  162. // Rollback schema write
  163. m.pool.WithClient(func(c *KVClient) error {
  164. return c.Delete(key)
  165. })
  166. return err
  167. }
  168. // Update cache
  169. m.cache[strings.ToLower(schema.Name)] = schema
  170. m.bumpVersionLocked()
  171. return nil
  172. }
  173. // DropTable drops a table.
  174. func (m *SchemaManager) DropTable(name string) error {
  175. tableLock := m.tableLock(name)
  176. tableLock.Lock()
  177. defer tableLock.Unlock()
  178. m.mu.Lock()
  179. defer m.mu.Unlock()
  180. key := m.schemaKey(name)
  181. // Check if table exists
  182. err := m.pool.WithClient(func(c *KVClient) error {
  183. _, err := c.Read(key)
  184. return err
  185. })
  186. if err != nil {
  187. return fmt.Errorf("table not found: %s", name)
  188. }
  189. // Delete all rows by scanning their actual keys and batch-deleting them, so
  190. // a table drop no longer leaks durable rows.
  191. if err := m.deleteKeysWithPrefix([]byte(fmt.Sprintf("%s:_data:%s:", m.database, strings.ToLower(name)))); err != nil {
  192. return err
  193. }
  194. // Delete schema
  195. err = m.pool.WithClient(func(c *KVClient) error {
  196. return c.Delete(key)
  197. })
  198. if err != nil {
  199. return fmt.Errorf("failed to delete schema: %w", err)
  200. }
  201. // Delete ROWID state.
  202. m.pool.WithClient(func(c *KVClient) error {
  203. return c.Delete(m.rowIDKey(name))
  204. })
  205. // Update catalog
  206. if err := m.removeFromCatalog(name); err != nil {
  207. return err
  208. }
  209. // Update cache
  210. tableLower := strings.ToLower(name)
  211. delete(m.cache, tableLower)
  212. delete(m.rowIDInitialized, tableLower)
  213. m.bumpVersionLocked()
  214. return nil
  215. }
  216. // GetSchema retrieves a table schema.
  217. func (m *SchemaManager) GetSchema(name string) (*Schema, error) {
  218. m.mu.RLock()
  219. if schema, ok := m.cache[strings.ToLower(name)]; ok {
  220. // Clone while still holding the read lock: the cached schema's
  221. // NextRowID field is mutated under the write lock, so cloning outside
  222. // the lock races with that mutation.
  223. cloned := cloneSchema(schema)
  224. m.mu.RUnlock()
  225. return cloned, nil
  226. }
  227. m.mu.RUnlock()
  228. m.mu.Lock()
  229. defer m.mu.Unlock()
  230. // Double-check after acquiring write lock
  231. if schema, ok := m.cache[strings.ToLower(name)]; ok {
  232. return cloneSchema(schema), nil
  233. }
  234. key := m.schemaKey(name)
  235. var data string
  236. err := m.pool.WithClient(func(c *KVClient) error {
  237. var err error
  238. data, err = c.Read(key)
  239. return err
  240. })
  241. if err != nil {
  242. if err == ErrKeyNotFound {
  243. return nil, fmt.Errorf("table not found: %s", name)
  244. }
  245. return nil, err
  246. }
  247. var schema Schema
  248. if err := json.Unmarshal([]byte(data), &schema); err != nil {
  249. return nil, fmt.Errorf("failed to parse schema: %w", err)
  250. }
  251. m.cache[strings.ToLower(name)] = &schema
  252. return cloneSchema(&schema), nil
  253. }
  254. func cloneSchema(schema *Schema) *Schema {
  255. if schema == nil {
  256. return nil
  257. }
  258. cloned := *schema
  259. cloned.Columns = append([]Column(nil), schema.Columns...)
  260. return &cloned
  261. }
  262. func cloneIndex(index *Index) *Index {
  263. if index == nil {
  264. return nil
  265. }
  266. cloned := *index
  267. cloned.Columns = append([]IndexColumn(nil), index.Columns...)
  268. return &cloned
  269. }
  270. // TableExists checks if a table exists.
  271. func (m *SchemaManager) TableExists(name string) bool {
  272. _, err := m.GetSchema(name)
  273. return err == nil
  274. }
  275. // ListTables returns all table names.
  276. func (m *SchemaManager) ListTables() ([]string, error) {
  277. var data string
  278. err := m.pool.WithClient(func(c *KVClient) error {
  279. var err error
  280. data, err = c.Read(m.catalogKey())
  281. return err
  282. })
  283. if err != nil {
  284. if err == ErrKeyNotFound {
  285. return nil, nil
  286. }
  287. return nil, err
  288. }
  289. var tables []string
  290. if err := json.Unmarshal([]byte(data), &tables); err != nil {
  291. return nil, fmt.Errorf("failed to parse catalog: %w", err)
  292. }
  293. return tables, nil
  294. }
  295. // addToCatalog adds a table to the catalog.
  296. func (m *SchemaManager) addToCatalog(name string) error {
  297. tables, err := m.ListTables()
  298. if err != nil && err != ErrKeyNotFound {
  299. return err
  300. }
  301. // Check if already exists
  302. lowerName := strings.ToLower(name)
  303. for _, t := range tables {
  304. if strings.ToLower(t) == lowerName {
  305. return nil
  306. }
  307. }
  308. tables = append(tables, name)
  309. data, err := json.Marshal(tables)
  310. if err != nil {
  311. return err
  312. }
  313. return m.pool.WithClient(func(c *KVClient) error {
  314. return c.Write(m.catalogKey(), string(data))
  315. })
  316. }
  317. // removeFromCatalog removes a table from the catalog.
  318. func (m *SchemaManager) removeFromCatalog(name string) error {
  319. tables, err := m.ListTables()
  320. if err != nil {
  321. return err
  322. }
  323. lowerName := strings.ToLower(name)
  324. newTables := make([]string, 0, len(tables))
  325. for _, t := range tables {
  326. if strings.ToLower(t) != lowerName {
  327. newTables = append(newTables, t)
  328. }
  329. }
  330. data, err := json.Marshal(newTables)
  331. if err != nil {
  332. return err
  333. }
  334. return m.pool.WithClient(func(c *KVClient) error {
  335. return c.Write(m.catalogKey(), string(data))
  336. })
  337. }
  338. // InvalidateCache clears the cache for a table.
  339. func (m *SchemaManager) InvalidateCache(name string) {
  340. m.mu.Lock()
  341. defer m.mu.Unlock()
  342. tableLower := strings.ToLower(name)
  343. delete(m.cache, tableLower)
  344. delete(m.rowIDInitialized, tableLower)
  345. }
  346. // ToAnalyzerTableInfo converts a Schema to analyzer.TableInfo.
  347. func (s *Schema) ToAnalyzerTableInfo() *analyzer.TableInfo {
  348. info := &analyzer.TableInfo{
  349. Name: s.Name,
  350. }
  351. for _, col := range s.Columns {
  352. info.Columns = append(info.Columns, analyzer.ColumnInfo{
  353. Name: col.Name,
  354. Type: analyzer.TypeFromName(col.Type),
  355. Nullable: col.Nullable,
  356. PrimaryKey: col.PrimaryKey,
  357. TableName: s.Name,
  358. })
  359. }
  360. return info
  361. }
  362. // GetColumn returns a column by name.
  363. func (s *Schema) GetColumn(name string) (*Column, bool) {
  364. lowerName := strings.ToLower(name)
  365. for i := range s.Columns {
  366. if strings.ToLower(s.Columns[i].Name) == lowerName {
  367. return &s.Columns[i], true
  368. }
  369. }
  370. return nil, false
  371. }
  372. // GetNextRowID gets and increments the next ROWID for a table.
  373. func (m *SchemaManager) GetNextRowID(table string) (int64, error) {
  374. m.mu.Lock()
  375. defer m.mu.Unlock()
  376. schema, err := m.getSchemaLocked(table)
  377. if err != nil {
  378. return 0, err
  379. }
  380. nextRowID, err := m.getNextRowIDLocked(schema)
  381. if err != nil {
  382. return 0, err
  383. }
  384. schema.NextRowID = nextRowID + 1
  385. return nextRowID, nil
  386. }
  387. // UpdateMaxRowID updates the next ROWID if the provided value is higher.
  388. func (m *SchemaManager) UpdateMaxRowID(table string, rowid int64) error {
  389. m.mu.Lock()
  390. defer m.mu.Unlock()
  391. schema, err := m.getSchemaLocked(table)
  392. if err != nil {
  393. return err
  394. }
  395. nextRowID, err := m.getNextRowIDLocked(schema)
  396. if err != nil {
  397. return err
  398. }
  399. if rowid >= nextRowID {
  400. schema.NextRowID = rowid + 1
  401. }
  402. return nil
  403. }
  404. // getNextRowIDLocked returns a table's in-memory ROWID counter (must hold lock).
  405. // On first use after startup, the counter is derived from durable row data so
  406. // ROWID movement does not add a separate WAL entry.
  407. func (m *SchemaManager) getNextRowIDLocked(schema *Schema) (int64, error) {
  408. tableLower := strings.ToLower(schema.Name)
  409. if m.rowIDInitialized[tableLower] {
  410. if schema.NextRowID < 1 {
  411. schema.NextRowID = 1
  412. }
  413. return schema.NextRowID, nil
  414. }
  415. nextRowID, err := m.deriveNextRowIDLocked(schema)
  416. if err != nil {
  417. return 0, err
  418. }
  419. if schema.NextRowID > nextRowID {
  420. nextRowID = schema.NextRowID
  421. }
  422. if nextRowID < 1 {
  423. nextRowID = 1
  424. }
  425. schema.NextRowID = nextRowID
  426. m.rowIDInitialized[tableLower] = true
  427. return schema.NextRowID, nil
  428. }
  429. // deriveNextRowIDLocked scans durable row keys to recover max(rowid)+1,
  430. // streaming each page through decodeRow instead of materializing every value.
  431. func (m *SchemaManager) deriveNextRowIDLocked(schema *Schema) (int64, error) {
  432. prefix := []byte(fmt.Sprintf("%s:_data:%s:", m.database, strings.ToLower(schema.Name)))
  433. var maxRowID int64
  434. err := m.pool.WithClient(func(client *KVClient) (retErr error) {
  435. cursor, err := client.Scan(prefix)
  436. if err != nil {
  437. return err
  438. }
  439. defer func() {
  440. if err := cursor.Close(); retErr == nil {
  441. retErr = err
  442. }
  443. }()
  444. for {
  445. entries, done, err := cursor.Next()
  446. if err != nil {
  447. return err
  448. }
  449. for _, e := range entries {
  450. row, err := decodeRow(e.Value)
  451. if err != nil {
  452. return fmt.Errorf("failed to parse row while deriving ROWID: %w", err)
  453. }
  454. if rowid, ok := valueAsInt64(row["_rowid_"]); ok && rowid > maxRowID {
  455. maxRowID = rowid
  456. }
  457. }
  458. if done {
  459. return nil
  460. }
  461. }
  462. })
  463. if err != nil {
  464. return 0, err
  465. }
  466. return maxRowID + 1, nil
  467. }
  468. // deleteKeysWithPrefix streams the keys with the given prefix one page at a
  469. // time and atomically batch-deletes them, so a bulk operation never leaves
  470. // durable rows behind.
  471. func (m *SchemaManager) deleteKeysWithPrefix(prefix []byte) error {
  472. return m.pool.WithClient(func(client *KVClient) (retErr error) {
  473. cursor, err := client.ScanKeys(prefix)
  474. if err != nil {
  475. return err
  476. }
  477. defer func() {
  478. if err := cursor.Close(); retErr == nil {
  479. retErr = err
  480. }
  481. }()
  482. ops := make([]BatchOp, 0, scanPageSize)
  483. batchBytes := 8
  484. flush := func() error {
  485. if len(ops) == 0 {
  486. return nil
  487. }
  488. if _, err := client.BatchWrite(ops, nil); err != nil {
  489. return err
  490. }
  491. ops = ops[:0]
  492. batchBytes = 8
  493. return nil
  494. }
  495. for {
  496. entries, done, err := cursor.Next()
  497. if err != nil {
  498. return err
  499. }
  500. for _, e := range entries {
  501. opBytes := 12 + len(e.Key)
  502. if len(ops) == maxOperations || batchBytes+opBytes > bulkBatchByteBudget {
  503. if err := flush(); err != nil {
  504. return err
  505. }
  506. }
  507. ops = append(ops, BatchOp{Op: batchDelete, Key: append([]byte(nil), e.Key...)})
  508. batchBytes += opBytes
  509. }
  510. if done {
  511. return flush()
  512. }
  513. }
  514. })
  515. }
  516. func valueAsInt64(value interface{}) (int64, bool) {
  517. switch v := value.(type) {
  518. case int64:
  519. return v, true
  520. case int:
  521. return int64(v), true
  522. case float64:
  523. return int64(v), true
  524. default:
  525. return 0, false
  526. }
  527. }
  528. // getSchemaLocked retrieves schema (must hold lock).
  529. func (m *SchemaManager) getSchemaLocked(name string) (*Schema, error) {
  530. if schema, ok := m.cache[strings.ToLower(name)]; ok {
  531. return schema, nil
  532. }
  533. key := m.schemaKey(name)
  534. var data string
  535. err := m.pool.WithClient(func(c *KVClient) error {
  536. var err error
  537. data, err = c.Read(key)
  538. return err
  539. })
  540. if err != nil {
  541. if err == ErrKeyNotFound {
  542. return nil, fmt.Errorf("table not found: %s", name)
  543. }
  544. return nil, err
  545. }
  546. var schema Schema
  547. if err := json.Unmarshal([]byte(data), &schema); err != nil {
  548. return nil, fmt.Errorf("failed to parse schema: %w", err)
  549. }
  550. m.cache[strings.ToLower(name)] = &schema
  551. return &schema, nil
  552. }
  553. // saveSchemaLocked saves schema (must hold lock).
  554. func (m *SchemaManager) saveSchemaLocked(schema *Schema) error {
  555. data, err := json.Marshal(schema)
  556. if err != nil {
  557. return fmt.Errorf("failed to serialize schema: %w", err)
  558. }
  559. key := m.schemaKey(schema.Name)
  560. err = m.pool.WithClient(func(c *KVClient) error {
  561. return c.Write(key, string(data))
  562. })
  563. if err != nil {
  564. return fmt.Errorf("failed to write schema: %w", err)
  565. }
  566. m.cache[strings.ToLower(schema.Name)] = schema
  567. return nil
  568. }
  569. // Index management methods
  570. // indexKey returns the key for an index.
  571. func (m *SchemaManager) indexKey(name string) string {
  572. return fmt.Sprintf("%s:index:%s", m.database, strings.ToLower(name))
  573. }
  574. // indexListKey returns the key for the index list.
  575. func (m *SchemaManager) indexListKey() string {
  576. return fmt.Sprintf("%s:indexes", m.database)
  577. }
  578. // CreateIndex creates a new index.
  579. func (m *SchemaManager) CreateIndex(index *Index) error {
  580. m.mu.Lock()
  581. defer m.mu.Unlock()
  582. // Check if index already exists
  583. key := m.indexKey(index.Name)
  584. err := m.pool.WithClient(func(c *KVClient) error {
  585. _, err := c.Read(key)
  586. return err
  587. })
  588. if err == nil {
  589. return fmt.Errorf("index already exists: %s", index.Name)
  590. }
  591. // Verify table exists
  592. if _, err := m.getSchemaLocked(index.Table); err != nil {
  593. return fmt.Errorf("table not found: %s", index.Table)
  594. }
  595. // Save index
  596. index.CreatedAt = time.Now()
  597. data, err := json.Marshal(index)
  598. if err != nil {
  599. return fmt.Errorf("failed to serialize index: %w", err)
  600. }
  601. err = m.pool.WithClient(func(c *KVClient) error {
  602. return c.Write(key, string(data))
  603. })
  604. if err != nil {
  605. return fmt.Errorf("failed to write index: %w", err)
  606. }
  607. // Add to index list
  608. if err := m.addToIndexList(index.Name); err != nil {
  609. return err
  610. }
  611. m.indexCache[strings.ToLower(index.Name)] = cloneIndex(index)
  612. m.bumpVersionLocked()
  613. return nil
  614. }
  615. // DropIndex drops an index.
  616. func (m *SchemaManager) DropIndex(name string) error {
  617. m.mu.Lock()
  618. defer m.mu.Unlock()
  619. key := m.indexKey(name)
  620. err := m.pool.WithClient(func(c *KVClient) error {
  621. return c.Delete(key)
  622. })
  623. if err != nil {
  624. return fmt.Errorf("failed to delete index: %w", err)
  625. }
  626. if err := m.removeFromIndexList(name); err != nil {
  627. return err
  628. }
  629. delete(m.indexCache, strings.ToLower(name))
  630. m.bumpVersionLocked()
  631. return nil
  632. }
  633. // IndexExists checks if an index exists.
  634. func (m *SchemaManager) IndexExists(name string) bool {
  635. m.mu.RLock()
  636. defer m.mu.RUnlock()
  637. if _, ok := m.indexCache[strings.ToLower(name)]; ok {
  638. return true
  639. }
  640. key := m.indexKey(name)
  641. err := m.pool.WithClient(func(c *KVClient) error {
  642. _, err := c.Read(key)
  643. return err
  644. })
  645. return err == nil
  646. }
  647. // GetIndex retrieves an index by name.
  648. func (m *SchemaManager) GetIndex(name string) (*Index, error) {
  649. m.mu.Lock()
  650. defer m.mu.Unlock()
  651. cacheKey := strings.ToLower(name)
  652. if index, ok := m.indexCache[cacheKey]; ok {
  653. return cloneIndex(index), nil
  654. }
  655. key := m.indexKey(name)
  656. var data string
  657. err := m.pool.WithClient(func(c *KVClient) error {
  658. var err error
  659. data, err = c.Read(key)
  660. return err
  661. })
  662. if err != nil {
  663. return nil, fmt.Errorf("index not found: %s", name)
  664. }
  665. var index Index
  666. if err := json.Unmarshal([]byte(data), &index); err != nil {
  667. return nil, fmt.Errorf("failed to parse index: %w", err)
  668. }
  669. m.indexCache[cacheKey] = &index
  670. return cloneIndex(&index), nil
  671. }
  672. // ListIndexes returns all index names.
  673. func (m *SchemaManager) ListIndexes() ([]string, error) {
  674. m.mu.Lock()
  675. defer m.mu.Unlock()
  676. if m.indexListCached {
  677. return append([]string(nil), m.indexListCache...), nil
  678. }
  679. key := m.indexListKey()
  680. var data string
  681. err := m.pool.WithClient(func(c *KVClient) error {
  682. var err error
  683. data, err = c.Read(key)
  684. return err
  685. })
  686. if err == ErrKeyNotFound {
  687. m.indexListCache = nil
  688. m.indexListCached = true
  689. return []string{}, nil
  690. }
  691. if err != nil {
  692. return nil, err
  693. }
  694. var indexes []string
  695. if err := json.Unmarshal([]byte(data), &indexes); err != nil {
  696. return []string{}, nil
  697. }
  698. m.indexListCache = append([]string(nil), indexes...)
  699. m.indexListCached = true
  700. return indexes, nil
  701. }
  702. // ListTableIndexes returns all indexes for a table.
  703. func (m *SchemaManager) ListTableIndexes(table string) ([]*Index, error) {
  704. indexes, err := m.ListIndexes()
  705. if err != nil {
  706. return nil, err
  707. }
  708. var result []*Index
  709. for _, name := range indexes {
  710. idx, err := m.GetIndex(name)
  711. if err != nil {
  712. continue
  713. }
  714. if strings.EqualFold(idx.Table, table) {
  715. result = append(result, idx)
  716. }
  717. }
  718. return result, nil
  719. }
  720. // addToIndexList adds an index name to the list.
  721. func (m *SchemaManager) addToIndexList(name string) error {
  722. key := m.indexListKey()
  723. var indexes []string
  724. var data string
  725. err := m.pool.WithClient(func(c *KVClient) error {
  726. var err error
  727. data, err = c.Read(key)
  728. return err
  729. })
  730. if err == nil {
  731. json.Unmarshal([]byte(data), &indexes)
  732. }
  733. indexes = append(indexes, name)
  734. newData, _ := json.Marshal(indexes)
  735. err = m.pool.WithClient(func(c *KVClient) error {
  736. return c.Write(key, string(newData))
  737. })
  738. if err == nil {
  739. m.indexListCache = append([]string(nil), indexes...)
  740. m.indexListCached = true
  741. }
  742. return err
  743. }
  744. // removeFromIndexList removes an index name from the list.
  745. func (m *SchemaManager) removeFromIndexList(name string) error {
  746. key := m.indexListKey()
  747. var indexes []string
  748. var data string
  749. err := m.pool.WithClient(func(c *KVClient) error {
  750. var err error
  751. data, err = c.Read(key)
  752. return err
  753. })
  754. if err != nil {
  755. return nil
  756. }
  757. json.Unmarshal([]byte(data), &indexes)
  758. var newIndexes []string
  759. for _, idx := range indexes {
  760. if !strings.EqualFold(idx, name) {
  761. newIndexes = append(newIndexes, idx)
  762. }
  763. }
  764. newData, _ := json.Marshal(newIndexes)
  765. err = m.pool.WithClient(func(c *KVClient) error {
  766. return c.Write(key, string(newData))
  767. })
  768. if err == nil {
  769. m.indexListCache = append([]string(nil), newIndexes...)
  770. m.indexListCached = true
  771. }
  772. return err
  773. }
  774. // AddColumn adds a new column to a table.
  775. func (m *SchemaManager) AddColumn(table string, column Column) error {
  776. m.mu.Lock()
  777. defer m.mu.Unlock()
  778. schema, err := m.getSchemaUnsafe(table)
  779. if err != nil {
  780. return err
  781. }
  782. // Check if column already exists
  783. for _, col := range schema.Columns {
  784. if strings.EqualFold(col.Name, column.Name) {
  785. return fmt.Errorf("column already exists: %s", column.Name)
  786. }
  787. }
  788. // Publish a fresh snapshot instead of mutating readers' shared pointer.
  789. schema = cloneSchema(schema)
  790. schema.Columns = append(schema.Columns, column)
  791. // Update schema
  792. return m.updateSchemaUnsafe(schema)
  793. }
  794. // DropColumn removes a column from a table.
  795. func (m *SchemaManager) DropColumn(table, columnName string) error {
  796. m.mu.Lock()
  797. defer m.mu.Unlock()
  798. schema, err := m.getSchemaUnsafe(table)
  799. if err != nil {
  800. return err
  801. }
  802. // Cannot drop primary key column
  803. if strings.EqualFold(schema.PrimaryKey, columnName) {
  804. return fmt.Errorf("cannot drop primary key column: %s", columnName)
  805. }
  806. // Find and remove column
  807. newColumns := make([]Column, 0, len(schema.Columns)-1)
  808. found := false
  809. for _, col := range schema.Columns {
  810. if strings.EqualFold(col.Name, columnName) {
  811. found = true
  812. continue
  813. }
  814. newColumns = append(newColumns, col)
  815. }
  816. if !found {
  817. return fmt.Errorf("column not found: %s", columnName)
  818. }
  819. schema = cloneSchema(schema)
  820. schema.Columns = newColumns
  821. // Update schema
  822. return m.updateSchemaUnsafe(schema)
  823. }
  824. // RenameTable renames a table.
  825. func (m *SchemaManager) RenameTable(oldName, newName string) error {
  826. m.mu.Lock()
  827. defer m.mu.Unlock()
  828. // Check if old table exists
  829. schema, err := m.getSchemaUnsafe(oldName)
  830. if err != nil {
  831. return err
  832. }
  833. // Check if new table name already exists
  834. _, err = m.getSchemaUnsafe(newName)
  835. if err == nil {
  836. return fmt.Errorf("table already exists: %s", newName)
  837. }
  838. schema = cloneSchema(schema)
  839. // Update schema name
  840. schema.Name = newName
  841. rowIDKey := m.rowIDKey(oldName)
  842. // Delete old schema
  843. oldKey := m.schemaKey(oldName)
  844. err = m.pool.WithClient(func(c *KVClient) error {
  845. return c.Delete(oldKey)
  846. })
  847. if err != nil {
  848. return err
  849. }
  850. // Remove from catalog
  851. m.removeFromCatalog(oldName)
  852. // Move ROWID state.
  853. m.pool.WithClient(func(c *KVClient) error {
  854. return c.Delete(rowIDKey)
  855. })
  856. // Update cache
  857. oldLower := strings.ToLower(oldName)
  858. newLower := strings.ToLower(newName)
  859. wasInitialized := m.rowIDInitialized[oldLower]
  860. delete(m.cache, oldLower)
  861. delete(m.rowIDInitialized, oldLower)
  862. // Write new schema
  863. newKey := m.schemaKey(newName)
  864. data, _ := json.Marshal(schema)
  865. err = m.pool.WithClient(func(c *KVClient) error {
  866. return c.Write(newKey, string(data))
  867. })
  868. if err != nil {
  869. return err
  870. }
  871. // Add to catalog
  872. m.addToCatalog(newName)
  873. // Update cache
  874. m.cache[newLower] = schema
  875. if wasInitialized {
  876. m.rowIDInitialized[newLower] = true
  877. }
  878. m.bumpVersionLocked()
  879. return nil
  880. }
  881. // RenameColumn renames a column in a table.
  882. func (m *SchemaManager) RenameColumn(table, oldName, newName string) error {
  883. m.mu.Lock()
  884. defer m.mu.Unlock()
  885. schema, err := m.getSchemaUnsafe(table)
  886. if err != nil {
  887. return err
  888. }
  889. // Check if new column name already exists
  890. for _, col := range schema.Columns {
  891. if strings.EqualFold(col.Name, newName) {
  892. return fmt.Errorf("column already exists: %s", newName)
  893. }
  894. }
  895. schema = cloneSchema(schema)
  896. // Find and rename column
  897. found := false
  898. for i, col := range schema.Columns {
  899. if strings.EqualFold(col.Name, oldName) {
  900. schema.Columns[i].Name = newName
  901. found = true
  902. // Update primary key reference if needed
  903. if strings.EqualFold(schema.PrimaryKey, oldName) {
  904. schema.PrimaryKey = newName
  905. }
  906. break
  907. }
  908. }
  909. if !found {
  910. return fmt.Errorf("column not found: %s", oldName)
  911. }
  912. // Update schema
  913. return m.updateSchemaUnsafe(schema)
  914. }
  915. // getSchemaUnsafe gets a schema without locking (internal use).
  916. func (m *SchemaManager) getSchemaUnsafe(table string) (*Schema, error) {
  917. tableLower := strings.ToLower(table)
  918. // Check cache
  919. if schema, ok := m.cache[tableLower]; ok {
  920. return schema, nil
  921. }
  922. // Read from storage
  923. key := m.schemaKey(table)
  924. var data string
  925. err := m.pool.WithClient(func(c *KVClient) error {
  926. var err error
  927. data, err = c.Read(key)
  928. return err
  929. })
  930. if err != nil {
  931. return nil, fmt.Errorf("table not found: %s", table)
  932. }
  933. var schema Schema
  934. if err := json.Unmarshal([]byte(data), &schema); err != nil {
  935. return nil, err
  936. }
  937. m.cache[tableLower] = &schema
  938. return &schema, nil
  939. }
  940. // updateSchemaUnsafe updates a schema without locking (internal use).
  941. func (m *SchemaManager) updateSchemaUnsafe(schema *Schema) error {
  942. key := m.schemaKey(schema.Name)
  943. data, _ := json.Marshal(schema)
  944. err := m.pool.WithClient(func(c *KVClient) error {
  945. return c.Write(key, string(data))
  946. })
  947. if err != nil {
  948. return err
  949. }
  950. // Update cache
  951. m.cache[strings.ToLower(schema.Name)] = schema
  952. m.bumpVersionLocked()
  953. return nil
  954. }