schema.go 23 KB

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