2
0

schema.go 26 KB

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