2
0

schema.go 23 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001
  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. // Clone while still holding the read lock: the cached schema's
  209. // NextRowID field is mutated under the write lock, so cloning outside
  210. // the lock races with that mutation.
  211. cloned := cloneSchema(schema)
  212. m.mu.RUnlock()
  213. return cloned, nil
  214. }
  215. m.mu.RUnlock()
  216. m.mu.Lock()
  217. defer m.mu.Unlock()
  218. // Double-check after acquiring write lock
  219. if schema, ok := m.cache[strings.ToLower(name)]; ok {
  220. return cloneSchema(schema), nil
  221. }
  222. key := m.schemaKey(name)
  223. var data string
  224. err := m.pool.WithClient(func(c *KVClient) error {
  225. var err error
  226. data, err = c.Read(key)
  227. return err
  228. })
  229. if err != nil {
  230. if err == ErrKeyNotFound {
  231. return nil, fmt.Errorf("table not found: %s", name)
  232. }
  233. return nil, err
  234. }
  235. var schema Schema
  236. if err := json.Unmarshal([]byte(data), &schema); err != nil {
  237. return nil, fmt.Errorf("failed to parse schema: %w", err)
  238. }
  239. m.cache[strings.ToLower(name)] = &schema
  240. return cloneSchema(&schema), nil
  241. }
  242. func cloneSchema(schema *Schema) *Schema {
  243. if schema == nil {
  244. return nil
  245. }
  246. cloned := *schema
  247. cloned.Columns = append([]Column(nil), schema.Columns...)
  248. return &cloned
  249. }
  250. // TableExists checks if a table exists.
  251. func (m *SchemaManager) TableExists(name string) bool {
  252. _, err := m.GetSchema(name)
  253. return err == nil
  254. }
  255. // ListTables returns all table names.
  256. func (m *SchemaManager) ListTables() ([]string, error) {
  257. var data string
  258. err := m.pool.WithClient(func(c *KVClient) error {
  259. var err error
  260. data, err = c.Read(m.catalogKey())
  261. return err
  262. })
  263. if err != nil {
  264. if err == ErrKeyNotFound {
  265. return nil, nil
  266. }
  267. return nil, err
  268. }
  269. var tables []string
  270. if err := json.Unmarshal([]byte(data), &tables); err != nil {
  271. return nil, fmt.Errorf("failed to parse catalog: %w", err)
  272. }
  273. return tables, nil
  274. }
  275. // addToCatalog adds a table to the catalog.
  276. func (m *SchemaManager) addToCatalog(name string) error {
  277. tables, err := m.ListTables()
  278. if err != nil && err != ErrKeyNotFound {
  279. return err
  280. }
  281. // Check if already exists
  282. lowerName := strings.ToLower(name)
  283. for _, t := range tables {
  284. if strings.ToLower(t) == lowerName {
  285. return nil
  286. }
  287. }
  288. tables = append(tables, name)
  289. data, err := json.Marshal(tables)
  290. if err != nil {
  291. return err
  292. }
  293. return m.pool.WithClient(func(c *KVClient) error {
  294. return c.Write(m.catalogKey(), string(data))
  295. })
  296. }
  297. // removeFromCatalog removes a table from the catalog.
  298. func (m *SchemaManager) removeFromCatalog(name string) error {
  299. tables, err := m.ListTables()
  300. if err != nil {
  301. return err
  302. }
  303. lowerName := strings.ToLower(name)
  304. newTables := make([]string, 0, len(tables))
  305. for _, t := range tables {
  306. if strings.ToLower(t) != lowerName {
  307. newTables = append(newTables, t)
  308. }
  309. }
  310. data, err := json.Marshal(newTables)
  311. if err != nil {
  312. return err
  313. }
  314. return m.pool.WithClient(func(c *KVClient) error {
  315. return c.Write(m.catalogKey(), string(data))
  316. })
  317. }
  318. // InvalidateCache clears the cache for a table.
  319. func (m *SchemaManager) InvalidateCache(name string) {
  320. m.mu.Lock()
  321. defer m.mu.Unlock()
  322. tableLower := strings.ToLower(name)
  323. delete(m.cache, tableLower)
  324. delete(m.rowIDInitialized, tableLower)
  325. }
  326. // ToAnalyzerTableInfo converts a Schema to analyzer.TableInfo.
  327. func (s *Schema) ToAnalyzerTableInfo() *analyzer.TableInfo {
  328. info := &analyzer.TableInfo{
  329. Name: s.Name,
  330. }
  331. for _, col := range s.Columns {
  332. info.Columns = append(info.Columns, analyzer.ColumnInfo{
  333. Name: col.Name,
  334. Type: analyzer.TypeFromName(col.Type),
  335. Nullable: col.Nullable,
  336. PrimaryKey: col.PrimaryKey,
  337. TableName: s.Name,
  338. })
  339. }
  340. return info
  341. }
  342. // GetColumn returns a column by name.
  343. func (s *Schema) GetColumn(name string) (*Column, bool) {
  344. lowerName := strings.ToLower(name)
  345. for i := range s.Columns {
  346. if strings.ToLower(s.Columns[i].Name) == lowerName {
  347. return &s.Columns[i], true
  348. }
  349. }
  350. return nil, false
  351. }
  352. // GetNextRowID gets and increments the next ROWID for a table.
  353. func (m *SchemaManager) GetNextRowID(table string) (int64, error) {
  354. m.mu.Lock()
  355. defer m.mu.Unlock()
  356. schema, err := m.getSchemaLocked(table)
  357. if err != nil {
  358. return 0, err
  359. }
  360. nextRowID, err := m.getNextRowIDLocked(schema)
  361. if err != nil {
  362. return 0, err
  363. }
  364. schema.NextRowID = nextRowID + 1
  365. return nextRowID, nil
  366. }
  367. // UpdateMaxRowID updates the next ROWID if the provided value is higher.
  368. func (m *SchemaManager) UpdateMaxRowID(table string, rowid int64) error {
  369. m.mu.Lock()
  370. defer m.mu.Unlock()
  371. schema, err := m.getSchemaLocked(table)
  372. if err != nil {
  373. return err
  374. }
  375. nextRowID, err := m.getNextRowIDLocked(schema)
  376. if err != nil {
  377. return err
  378. }
  379. if rowid >= nextRowID {
  380. schema.NextRowID = rowid + 1
  381. }
  382. return nil
  383. }
  384. // getNextRowIDLocked returns a table's in-memory ROWID counter (must hold lock).
  385. // On first use after startup, the counter is derived from durable row data so
  386. // ROWID movement does not add a separate WAL entry.
  387. func (m *SchemaManager) getNextRowIDLocked(schema *Schema) (int64, error) {
  388. tableLower := strings.ToLower(schema.Name)
  389. if m.rowIDInitialized[tableLower] {
  390. if schema.NextRowID < 1 {
  391. schema.NextRowID = 1
  392. }
  393. return schema.NextRowID, nil
  394. }
  395. nextRowID, err := m.deriveNextRowIDLocked(schema)
  396. if err != nil {
  397. return 0, err
  398. }
  399. if schema.NextRowID > nextRowID {
  400. nextRowID = schema.NextRowID
  401. }
  402. if nextRowID < 1 {
  403. nextRowID = 1
  404. }
  405. schema.NextRowID = nextRowID
  406. m.rowIDInitialized[tableLower] = true
  407. return schema.NextRowID, nil
  408. }
  409. // deriveNextRowIDLocked scans durable row values to recover max(rowid)+1.
  410. func (m *SchemaManager) deriveNextRowIDLocked(schema *Schema) (int64, error) {
  411. prefix := fmt.Sprintf("%s:_data:%s:", m.database, strings.ToLower(schema.Name))
  412. var values []string
  413. err := m.pool.WithClient(func(c *KVClient) error {
  414. var err error
  415. values, err = c.Reads(prefix)
  416. return err
  417. })
  418. if err != nil {
  419. return 0, err
  420. }
  421. var maxRowID int64
  422. for _, value := range values {
  423. var row Row
  424. if err := json.Unmarshal([]byte(value), &row); err != nil {
  425. return 0, fmt.Errorf("failed to parse row while deriving ROWID: %w", err)
  426. }
  427. if rowid, ok := valueAsInt64(row["_rowid_"]); ok && rowid > maxRowID {
  428. maxRowID = rowid
  429. }
  430. }
  431. return maxRowID + 1, nil
  432. }
  433. func valueAsInt64(value interface{}) (int64, bool) {
  434. switch v := value.(type) {
  435. case int64:
  436. return v, true
  437. case int:
  438. return int64(v), true
  439. case float64:
  440. return int64(v), true
  441. default:
  442. return 0, false
  443. }
  444. }
  445. // getSchemaLocked retrieves schema (must hold lock).
  446. func (m *SchemaManager) getSchemaLocked(name string) (*Schema, error) {
  447. if schema, ok := m.cache[strings.ToLower(name)]; ok {
  448. return schema, nil
  449. }
  450. key := m.schemaKey(name)
  451. var data string
  452. err := m.pool.WithClient(func(c *KVClient) error {
  453. var err error
  454. data, err = c.Read(key)
  455. return err
  456. })
  457. if err != nil {
  458. if err == ErrKeyNotFound {
  459. return nil, fmt.Errorf("table not found: %s", name)
  460. }
  461. return nil, err
  462. }
  463. var schema Schema
  464. if err := json.Unmarshal([]byte(data), &schema); err != nil {
  465. return nil, fmt.Errorf("failed to parse schema: %w", err)
  466. }
  467. m.cache[strings.ToLower(name)] = &schema
  468. return &schema, nil
  469. }
  470. // saveSchemaLocked saves schema (must hold lock).
  471. func (m *SchemaManager) saveSchemaLocked(schema *Schema) error {
  472. data, err := json.Marshal(schema)
  473. if err != nil {
  474. return fmt.Errorf("failed to serialize schema: %w", err)
  475. }
  476. key := m.schemaKey(schema.Name)
  477. err = m.pool.WithClient(func(c *KVClient) error {
  478. return c.Write(key, string(data))
  479. })
  480. if err != nil {
  481. return fmt.Errorf("failed to write schema: %w", err)
  482. }
  483. m.cache[strings.ToLower(schema.Name)] = schema
  484. return nil
  485. }
  486. // Index management methods
  487. // indexKey returns the key for an index.
  488. func (m *SchemaManager) indexKey(name string) string {
  489. return fmt.Sprintf("%s:index:%s", m.database, strings.ToLower(name))
  490. }
  491. // indexListKey returns the key for the index list.
  492. func (m *SchemaManager) indexListKey() string {
  493. return fmt.Sprintf("%s:indexes", m.database)
  494. }
  495. // CreateIndex creates a new index.
  496. func (m *SchemaManager) CreateIndex(index *Index) error {
  497. m.mu.Lock()
  498. defer m.mu.Unlock()
  499. // Check if index already exists
  500. key := m.indexKey(index.Name)
  501. err := m.pool.WithClient(func(c *KVClient) error {
  502. _, err := c.Read(key)
  503. return err
  504. })
  505. if err == nil {
  506. return fmt.Errorf("index already exists: %s", index.Name)
  507. }
  508. // Verify table exists
  509. if _, err := m.getSchemaLocked(index.Table); err != nil {
  510. return fmt.Errorf("table not found: %s", index.Table)
  511. }
  512. // Save index
  513. index.CreatedAt = time.Now()
  514. data, err := json.Marshal(index)
  515. if err != nil {
  516. return fmt.Errorf("failed to serialize index: %w", err)
  517. }
  518. err = m.pool.WithClient(func(c *KVClient) error {
  519. return c.Write(key, string(data))
  520. })
  521. if err != nil {
  522. return fmt.Errorf("failed to write index: %w", err)
  523. }
  524. // Add to index list
  525. if err := m.addToIndexList(index.Name); err != nil {
  526. return err
  527. }
  528. m.bumpVersionLocked()
  529. return nil
  530. }
  531. // DropIndex drops an index.
  532. func (m *SchemaManager) DropIndex(name string) error {
  533. m.mu.Lock()
  534. defer m.mu.Unlock()
  535. key := m.indexKey(name)
  536. err := m.pool.WithClient(func(c *KVClient) error {
  537. return c.Delete(key)
  538. })
  539. if err != nil {
  540. return fmt.Errorf("failed to delete index: %w", err)
  541. }
  542. if err := m.removeFromIndexList(name); err != nil {
  543. return err
  544. }
  545. m.bumpVersionLocked()
  546. return nil
  547. }
  548. // IndexExists checks if an index exists.
  549. func (m *SchemaManager) IndexExists(name string) bool {
  550. m.mu.RLock()
  551. defer m.mu.RUnlock()
  552. key := m.indexKey(name)
  553. err := m.pool.WithClient(func(c *KVClient) error {
  554. _, err := c.Read(key)
  555. return err
  556. })
  557. return err == nil
  558. }
  559. // GetIndex retrieves an index by name.
  560. func (m *SchemaManager) GetIndex(name string) (*Index, error) {
  561. m.mu.RLock()
  562. defer m.mu.RUnlock()
  563. key := m.indexKey(name)
  564. var data string
  565. err := m.pool.WithClient(func(c *KVClient) error {
  566. var err error
  567. data, err = c.Read(key)
  568. return err
  569. })
  570. if err != nil {
  571. return nil, fmt.Errorf("index not found: %s", name)
  572. }
  573. var index Index
  574. if err := json.Unmarshal([]byte(data), &index); err != nil {
  575. return nil, fmt.Errorf("failed to parse index: %w", err)
  576. }
  577. return &index, nil
  578. }
  579. // ListIndexes returns all index names.
  580. func (m *SchemaManager) ListIndexes() ([]string, error) {
  581. m.mu.RLock()
  582. defer m.mu.RUnlock()
  583. key := m.indexListKey()
  584. var data string
  585. err := m.pool.WithClient(func(c *KVClient) error {
  586. var err error
  587. data, err = c.Read(key)
  588. return err
  589. })
  590. if err != nil {
  591. return []string{}, nil
  592. }
  593. var indexes []string
  594. if err := json.Unmarshal([]byte(data), &indexes); err != nil {
  595. return []string{}, nil
  596. }
  597. return indexes, nil
  598. }
  599. // ListTableIndexes returns all indexes for a table.
  600. func (m *SchemaManager) ListTableIndexes(table string) ([]*Index, error) {
  601. indexes, err := m.ListIndexes()
  602. if err != nil {
  603. return nil, err
  604. }
  605. var result []*Index
  606. for _, name := range indexes {
  607. idx, err := m.GetIndex(name)
  608. if err != nil {
  609. continue
  610. }
  611. if strings.EqualFold(idx.Table, table) {
  612. result = append(result, idx)
  613. }
  614. }
  615. return result, nil
  616. }
  617. // addToIndexList adds an index name to the list.
  618. func (m *SchemaManager) addToIndexList(name string) error {
  619. key := m.indexListKey()
  620. var indexes []string
  621. var data string
  622. err := m.pool.WithClient(func(c *KVClient) error {
  623. var err error
  624. data, err = c.Read(key)
  625. return err
  626. })
  627. if err == nil {
  628. json.Unmarshal([]byte(data), &indexes)
  629. }
  630. indexes = append(indexes, name)
  631. newData, _ := json.Marshal(indexes)
  632. return m.pool.WithClient(func(c *KVClient) error {
  633. return c.Write(key, string(newData))
  634. })
  635. }
  636. // removeFromIndexList removes an index name from the list.
  637. func (m *SchemaManager) removeFromIndexList(name string) error {
  638. key := m.indexListKey()
  639. var indexes []string
  640. var data string
  641. err := m.pool.WithClient(func(c *KVClient) error {
  642. var err error
  643. data, err = c.Read(key)
  644. return err
  645. })
  646. if err != nil {
  647. return nil
  648. }
  649. json.Unmarshal([]byte(data), &indexes)
  650. var newIndexes []string
  651. for _, idx := range indexes {
  652. if !strings.EqualFold(idx, name) {
  653. newIndexes = append(newIndexes, idx)
  654. }
  655. }
  656. newData, _ := json.Marshal(newIndexes)
  657. return m.pool.WithClient(func(c *KVClient) error {
  658. return c.Write(key, string(newData))
  659. })
  660. }
  661. // AddColumn adds a new column to a table.
  662. func (m *SchemaManager) AddColumn(table string, column Column) error {
  663. m.mu.Lock()
  664. defer m.mu.Unlock()
  665. schema, err := m.getSchemaUnsafe(table)
  666. if err != nil {
  667. return err
  668. }
  669. // Check if column already exists
  670. for _, col := range schema.Columns {
  671. if strings.EqualFold(col.Name, column.Name) {
  672. return fmt.Errorf("column already exists: %s", column.Name)
  673. }
  674. }
  675. // Publish a fresh snapshot instead of mutating readers' shared pointer.
  676. schema = cloneSchema(schema)
  677. schema.Columns = append(schema.Columns, column)
  678. // Update schema
  679. return m.updateSchemaUnsafe(schema)
  680. }
  681. // DropColumn removes a column from a table.
  682. func (m *SchemaManager) DropColumn(table, columnName string) error {
  683. m.mu.Lock()
  684. defer m.mu.Unlock()
  685. schema, err := m.getSchemaUnsafe(table)
  686. if err != nil {
  687. return err
  688. }
  689. // Cannot drop primary key column
  690. if strings.EqualFold(schema.PrimaryKey, columnName) {
  691. return fmt.Errorf("cannot drop primary key column: %s", columnName)
  692. }
  693. // Find and remove column
  694. newColumns := make([]Column, 0, len(schema.Columns)-1)
  695. found := false
  696. for _, col := range schema.Columns {
  697. if strings.EqualFold(col.Name, columnName) {
  698. found = true
  699. continue
  700. }
  701. newColumns = append(newColumns, col)
  702. }
  703. if !found {
  704. return fmt.Errorf("column not found: %s", columnName)
  705. }
  706. schema = cloneSchema(schema)
  707. schema.Columns = newColumns
  708. // Update schema
  709. return m.updateSchemaUnsafe(schema)
  710. }
  711. // RenameTable renames a table.
  712. func (m *SchemaManager) RenameTable(oldName, newName string) error {
  713. m.mu.Lock()
  714. defer m.mu.Unlock()
  715. // Check if old table exists
  716. schema, err := m.getSchemaUnsafe(oldName)
  717. if err != nil {
  718. return err
  719. }
  720. // Check if new table name already exists
  721. _, err = m.getSchemaUnsafe(newName)
  722. if err == nil {
  723. return fmt.Errorf("table already exists: %s", newName)
  724. }
  725. schema = cloneSchema(schema)
  726. // Update schema name
  727. schema.Name = newName
  728. rowIDKey := m.rowIDKey(oldName)
  729. // Delete old schema
  730. oldKey := m.schemaKey(oldName)
  731. err = m.pool.WithClient(func(c *KVClient) error {
  732. return c.Delete(oldKey)
  733. })
  734. if err != nil {
  735. return err
  736. }
  737. // Remove from catalog
  738. m.removeFromCatalog(oldName)
  739. // Move ROWID state.
  740. m.pool.WithClient(func(c *KVClient) error {
  741. return c.Delete(rowIDKey)
  742. })
  743. // Update cache
  744. oldLower := strings.ToLower(oldName)
  745. newLower := strings.ToLower(newName)
  746. wasInitialized := m.rowIDInitialized[oldLower]
  747. delete(m.cache, oldLower)
  748. delete(m.rowIDInitialized, oldLower)
  749. // Write new schema
  750. newKey := m.schemaKey(newName)
  751. data, _ := json.Marshal(schema)
  752. err = m.pool.WithClient(func(c *KVClient) error {
  753. return c.Write(newKey, string(data))
  754. })
  755. if err != nil {
  756. return err
  757. }
  758. // Add to catalog
  759. m.addToCatalog(newName)
  760. // Update cache
  761. m.cache[newLower] = schema
  762. if wasInitialized {
  763. m.rowIDInitialized[newLower] = true
  764. }
  765. m.bumpVersionLocked()
  766. return nil
  767. }
  768. // RenameColumn renames a column in a table.
  769. func (m *SchemaManager) RenameColumn(table, oldName, newName string) error {
  770. m.mu.Lock()
  771. defer m.mu.Unlock()
  772. schema, err := m.getSchemaUnsafe(table)
  773. if err != nil {
  774. return err
  775. }
  776. // Check if new column name already exists
  777. for _, col := range schema.Columns {
  778. if strings.EqualFold(col.Name, newName) {
  779. return fmt.Errorf("column already exists: %s", newName)
  780. }
  781. }
  782. schema = cloneSchema(schema)
  783. // Find and rename column
  784. found := false
  785. for i, col := range schema.Columns {
  786. if strings.EqualFold(col.Name, oldName) {
  787. schema.Columns[i].Name = newName
  788. found = true
  789. // Update primary key reference if needed
  790. if strings.EqualFold(schema.PrimaryKey, oldName) {
  791. schema.PrimaryKey = newName
  792. }
  793. break
  794. }
  795. }
  796. if !found {
  797. return fmt.Errorf("column not found: %s", oldName)
  798. }
  799. // Update schema
  800. return m.updateSchemaUnsafe(schema)
  801. }
  802. // getSchemaUnsafe gets a schema without locking (internal use).
  803. func (m *SchemaManager) getSchemaUnsafe(table string) (*Schema, error) {
  804. tableLower := strings.ToLower(table)
  805. // Check cache
  806. if schema, ok := m.cache[tableLower]; ok {
  807. return schema, nil
  808. }
  809. // Read from storage
  810. key := m.schemaKey(table)
  811. var data string
  812. err := m.pool.WithClient(func(c *KVClient) error {
  813. var err error
  814. data, err = c.Read(key)
  815. return err
  816. })
  817. if err != nil {
  818. return nil, fmt.Errorf("table not found: %s", table)
  819. }
  820. var schema Schema
  821. if err := json.Unmarshal([]byte(data), &schema); err != nil {
  822. return nil, err
  823. }
  824. m.cache[tableLower] = &schema
  825. return &schema, nil
  826. }
  827. // updateSchemaUnsafe updates a schema without locking (internal use).
  828. func (m *SchemaManager) updateSchemaUnsafe(schema *Schema) error {
  829. key := m.schemaKey(schema.Name)
  830. data, _ := json.Marshal(schema)
  831. err := m.pool.WithClient(func(c *KVClient) error {
  832. return c.Write(key, string(data))
  833. })
  834. if err != nil {
  835. return err
  836. }
  837. // Update cache
  838. m.cache[strings.ToLower(schema.Name)] = schema
  839. m.bumpVersionLocked()
  840. return nil
  841. }