| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 |
- package storage
- import (
- "encoding/json"
- "fmt"
- "strings"
- )
- // Row represents a database row.
- type Row map[string]interface{}
- // TableManager manages table data operations.
- type TableManager struct {
- pool *KVPool
- schema *SchemaManager
- database string
- }
- // NewTableManager creates a new table manager.
- func NewTableManager(pool *KVPool, schema *SchemaManager, database string) *TableManager {
- return &TableManager{
- pool: pool,
- schema: schema,
- database: database,
- }
- }
- // dataKey returns the key for a row.
- func (m *TableManager) dataKey(table, pk string) string {
- return fmt.Sprintf("%s:_data:%s:%s", m.database, strings.ToLower(table), pk)
- }
- // dataPrefix returns the prefix for all rows in a table.
- func (m *TableManager) dataPrefix(table string) string {
- return fmt.Sprintf("%s:_data:%s:", m.database, strings.ToLower(table))
- }
- // Insert inserts a new row.
- func (m *TableManager) Insert(table string, row Row) error {
- schema, err := m.schema.GetSchema(table)
- if err != nil {
- return err
- }
- // Get primary key value
- pkValue, ok := row[schema.PrimaryKey]
- if !ok {
- // Try case-insensitive lookup
- for k, v := range row {
- if strings.EqualFold(k, schema.PrimaryKey) {
- pkValue = v
- ok = true
- break
- }
- }
- }
- // Check if PK is INTEGER PRIMARY KEY (implicit ROWID alias)
- pkCol, _ := schema.GetColumn(schema.PrimaryKey)
- isIntegerPK := pkCol != nil && isIntegerType(pkCol.Type)
- // Auto-generate ROWID if no primary key provided or if it's INTEGER PRIMARY KEY
- var rowid int64
- if !ok || pkValue == nil {
- if isIntegerPK || !ok {
- // Generate ROWID
- rowid, err = m.schema.GetNextRowID(table)
- if err != nil {
- return err
- }
- pkValue = rowid
- row[schema.PrimaryKey] = rowid
- ok = true
- } else {
- return fmt.Errorf("missing primary key: %s", schema.PrimaryKey)
- }
- } else if isIntegerPK {
- // User provided INTEGER PRIMARY KEY value - track it
- switch v := pkValue.(type) {
- case int64:
- rowid = v
- case float64:
- rowid = int64(v)
- case int:
- rowid = int64(v)
- default:
- rowid = 0
- }
- if rowid > 0 {
- m.schema.UpdateMaxRowID(table, rowid)
- }
- }
- pk := fmt.Sprintf("%v", pkValue)
- // Check for duplicate
- key := m.dataKey(table, pk)
- err = m.pool.WithClient(func(c *KVClient) error {
- _, err := c.Read(key)
- return err
- })
- if err == nil {
- return fmt.Errorf("duplicate primary key: %s", pk)
- }
- // Validate required columns
- for _, col := range schema.Columns {
- if !col.Nullable && col.Default == nil {
- val, hasVal := row[col.Name]
- if !hasVal {
- // Try case-insensitive lookup
- for k, v := range row {
- if strings.EqualFold(k, col.Name) {
- val = v
- hasVal = true
- break
- }
- }
- }
- if !hasVal || val == nil {
- return fmt.Errorf("missing required column: %s", col.Name)
- }
- }
- }
- // Normalize column names to match schema
- normalizedRow := make(Row)
- for _, col := range schema.Columns {
- for k, v := range row {
- if strings.EqualFold(k, col.Name) {
- normalizedRow[col.Name] = v
- break
- }
- }
- }
- // Apply defaults
- for _, col := range schema.Columns {
- if _, ok := normalizedRow[col.Name]; !ok && col.Default != nil {
- normalizedRow[col.Name] = col.Default
- }
- }
- // Store ROWID (use PK value for INTEGER PRIMARY KEY, otherwise generate)
- if rowid > 0 {
- normalizedRow["_rowid_"] = rowid
- } else {
- // Generate ROWID for non-integer primary keys
- newRowID, _ := m.schema.GetNextRowID(table)
- normalizedRow["_rowid_"] = newRowID
- }
- // Serialize row
- data, err := json.Marshal(normalizedRow)
- if err != nil {
- return fmt.Errorf("failed to serialize row: %w", err)
- }
- // Write row
- err = m.pool.WithClient(func(c *KVClient) error {
- return c.Write(key, string(data))
- })
- if err != nil {
- return err
- }
- // Update indexes
- m.updateIndexesForRow(table, normalizedRow, true)
- return nil
- }
- // updateIndexesForRow adds or removes index entries for a row.
- func (m *TableManager) updateIndexesForRow(table string, row Row, add bool) {
- indexes, err := m.schema.ListTableIndexes(table)
- if err != nil || len(indexes) == 0 {
- return
- }
- rowid, ok := row["_rowid_"].(float64)
- if !ok {
- if rid, ok := row["_rowid_"].(int64); ok {
- rowid = float64(rid)
- } else {
- return
- }
- }
- for _, idx := range indexes {
- columns := make([]string, len(idx.Columns))
- for i, col := range idx.Columns {
- columns[i] = col.Name
- }
- colValue := m.buildIndexValue(row, columns)
- if add {
- m.AddIndexEntry(idx.Name, colValue, int64(rowid))
- } else {
- m.RemoveIndexEntry(idx.Name, colValue, int64(rowid))
- }
- }
- }
- // Select retrieves rows from a table.
- func (m *TableManager) Select(table string, filter func(Row) bool) ([]Row, error) {
- if !m.schema.TableExists(table) {
- return nil, fmt.Errorf("table not found: %s", table)
- }
- prefix := m.dataPrefix(table)
- var values []string
- err := m.pool.WithClient(func(c *KVClient) error {
- var err error
- values, err = c.Reads(prefix)
- return err
- })
- if err != nil {
- return nil, err
- }
- rows := make([]Row, 0, len(values))
- for _, data := range values {
- var row Row
- if err := json.Unmarshal([]byte(data), &row); err != nil {
- continue // Skip invalid rows
- }
- if filter == nil || filter(row) {
- rows = append(rows, row)
- }
- }
- return rows, nil
- }
- // SelectWithLimit retrieves rows with limit and offset.
- func (m *TableManager) SelectWithLimit(table string, filter func(Row) bool, limit, offset int) ([]Row, error) {
- rows, err := m.Select(table, filter)
- if err != nil {
- return nil, err
- }
- // Apply offset
- if offset > 0 {
- if offset >= len(rows) {
- return nil, nil
- }
- rows = rows[offset:]
- }
- // Apply limit
- if limit > 0 && limit < len(rows) {
- rows = rows[:limit]
- }
- return rows, nil
- }
- // Update updates rows matching the filter.
- func (m *TableManager) Update(table string, updates Row, filter func(Row) bool) (int, error) {
- schema, err := m.schema.GetSchema(table)
- if err != nil {
- return 0, err
- }
- // Get all rows
- rows, err := m.Select(table, filter)
- if err != nil {
- return 0, err
- }
- count := 0
- for _, row := range rows {
- // Remove old index entries before update
- m.updateIndexesForRow(table, row, false)
- // Apply updates
- for k, v := range updates {
- // Normalize column name
- for _, col := range schema.Columns {
- if strings.EqualFold(k, col.Name) {
- row[col.Name] = v
- break
- }
- }
- }
- // Get primary key
- pkValue := row[schema.PrimaryKey]
- pk := fmt.Sprintf("%v", pkValue)
- // Serialize row
- data, err := json.Marshal(row)
- if err != nil {
- continue
- }
- // Write back
- key := m.dataKey(table, pk)
- err = m.pool.WithClient(func(c *KVClient) error {
- return c.Write(key, string(data))
- })
- if err == nil {
- // Add new index entries after update
- m.updateIndexesForRow(table, row, true)
- count++
- }
- }
- return count, nil
- }
- // UpdateFunc updates rows matching the filter using a function to compute new values.
- // The updateFn receives the current row and returns the updates to apply.
- func (m *TableManager) UpdateFunc(table string, updateFn func(Row) (Row, error), filter func(Row) bool) (int, error) {
- schema, err := m.schema.GetSchema(table)
- if err != nil {
- return 0, err
- }
- // Get all rows
- rows, err := m.Select(table, filter)
- if err != nil {
- return 0, err
- }
- count := 0
- for _, row := range rows {
- // Remove old index entries before update
- m.updateIndexesForRow(table, row, false)
- // Compute updates using the provided function
- updates, err := updateFn(row)
- if err != nil {
- return count, err
- }
- // Apply updates
- for k, v := range updates {
- // Normalize column name
- for _, col := range schema.Columns {
- if strings.EqualFold(k, col.Name) {
- row[col.Name] = v
- break
- }
- }
- }
- // Get primary key
- pkValue := row[schema.PrimaryKey]
- pk := fmt.Sprintf("%v", pkValue)
- // Serialize row
- data, err := json.Marshal(row)
- if err != nil {
- continue
- }
- // Write back
- key := m.dataKey(table, pk)
- err = m.pool.WithClient(func(c *KVClient) error {
- return c.Write(key, string(data))
- })
- if err == nil {
- // Add new index entries after update
- m.updateIndexesForRow(table, row, true)
- count++
- }
- }
- return count, nil
- }
- // Delete deletes rows matching the filter.
- func (m *TableManager) Delete(table string, filter func(Row) bool) (int, error) {
- schema, err := m.schema.GetSchema(table)
- if err != nil {
- return 0, err
- }
- // Get all rows
- rows, err := m.Select(table, filter)
- if err != nil {
- return 0, err
- }
- count := 0
- for _, row := range rows {
- // Remove index entries before deleting row
- m.updateIndexesForRow(table, row, false)
- pkValue := row[schema.PrimaryKey]
- pk := fmt.Sprintf("%v", pkValue)
- key := m.dataKey(table, pk)
- err = m.pool.WithClient(func(c *KVClient) error {
- return c.Delete(key)
- })
- if err == nil {
- count++
- }
- }
- return count, nil
- }
- // GetByPK retrieves a row by primary key.
- func (m *TableManager) GetByPK(table string, pk string) (Row, error) {
- if !m.schema.TableExists(table) {
- return nil, fmt.Errorf("table not found: %s", table)
- }
- key := m.dataKey(table, pk)
- var data string
- err := m.pool.WithClient(func(c *KVClient) error {
- var err error
- data, err = c.Read(key)
- return err
- })
- if err != nil {
- if err == ErrKeyNotFound {
- return nil, fmt.Errorf("row not found: %s", pk)
- }
- return nil, err
- }
- var row Row
- if err := json.Unmarshal([]byte(data), &row); err != nil {
- return nil, fmt.Errorf("failed to parse row: %w", err)
- }
- return row, nil
- }
- // Count returns the number of rows in a table.
- func (m *TableManager) Count(table string, filter func(Row) bool) (int, error) {
- rows, err := m.Select(table, filter)
- if err != nil {
- return 0, err
- }
- return len(rows), nil
- }
- // Truncate removes all rows from a table.
- func (m *TableManager) Truncate(table string) (int, error) {
- return m.Delete(table, nil)
- }
- // isIntegerType checks if a type name is an integer type.
- func isIntegerType(typeName string) bool {
- t := strings.ToUpper(typeName)
- switch t {
- case "INTEGER", "INT", "SMALLINT", "BIGINT", "TINYINT", "MEDIUMINT":
- return true
- }
- return false
- }
- // IsRowIDColumn checks if a column name is a ROWID alias.
- func IsRowIDColumn(name string) bool {
- n := strings.ToLower(name)
- return n == "rowid" || n == "oid" || n == "_rowid_"
- }
- // Index entry methods - leveraging radix trie for prefix-based lookups
- // Format: {database}:idx:{index_name}:{column_value} → JSON array of rowids
- // indexEntryKey returns the key for an index entry.
- func (m *TableManager) indexEntryKey(indexName string, colValue interface{}) string {
- // Format the value without scientific notation
- var valueStr string
- switch v := colValue.(type) {
- case float64:
- // Check if it's actually an integer value
- if v == float64(int64(v)) {
- valueStr = fmt.Sprintf("%d", int64(v))
- } else {
- valueStr = fmt.Sprintf("%f", v)
- }
- case int64:
- valueStr = fmt.Sprintf("%d", v)
- case int:
- valueStr = fmt.Sprintf("%d", v)
- default:
- valueStr = fmt.Sprintf("%v", v)
- }
- return fmt.Sprintf("%s:idx:%s:%s", m.database, strings.ToLower(indexName), valueStr)
- }
- // indexPrefix returns the prefix for all entries of an index.
- func (m *TableManager) indexPrefix(indexName string) string {
- return fmt.Sprintf("%s:idx:%s:", m.database, strings.ToLower(indexName))
- }
- // AddIndexEntry adds a rowid to an index entry.
- func (m *TableManager) AddIndexEntry(indexName string, colValue interface{}, rowid int64) error {
- key := m.indexEntryKey(indexName, colValue)
- // Read existing rowids
- var rowids []int64
- err := m.pool.WithClient(func(c *KVClient) error {
- data, err := c.Read(key)
- if err == nil && data != "" {
- json.Unmarshal([]byte(data), &rowids)
- }
- return nil // Ignore not found errors
- })
- if err != nil {
- return err
- }
- // Add new rowid if not already present
- for _, r := range rowids {
- if r == rowid {
- return nil // Already exists
- }
- }
- rowids = append(rowids, rowid)
- // Write back
- data, _ := json.Marshal(rowids)
- return m.pool.WithClient(func(c *KVClient) error {
- return c.Write(key, string(data))
- })
- }
- // RemoveIndexEntry removes a rowid from an index entry.
- func (m *TableManager) RemoveIndexEntry(indexName string, colValue interface{}, rowid int64) error {
- key := m.indexEntryKey(indexName, colValue)
- // Read existing rowids
- var rowids []int64
- err := m.pool.WithClient(func(c *KVClient) error {
- data, err := c.Read(key)
- if err != nil {
- return err
- }
- json.Unmarshal([]byte(data), &rowids)
- return nil
- })
- if err != nil {
- return nil // Entry doesn't exist
- }
- // Remove rowid
- newRowids := make([]int64, 0, len(rowids))
- for _, r := range rowids {
- if r != rowid {
- newRowids = append(newRowids, r)
- }
- }
- if len(newRowids) == 0 {
- // Delete the entry entirely
- return m.pool.WithClient(func(c *KVClient) error {
- return c.Delete(key)
- })
- }
- // Write back
- data, _ := json.Marshal(newRowids)
- return m.pool.WithClient(func(c *KVClient) error {
- return c.Write(key, string(data))
- })
- }
- // LookupIndex returns rowids matching a column value using the index.
- func (m *TableManager) LookupIndex(indexName string, colValue interface{}) ([]int64, error) {
- key := m.indexEntryKey(indexName, colValue)
- var rowids []int64
- err := m.pool.WithClient(func(c *KVClient) error {
- data, err := c.Read(key)
- if err != nil {
- return err
- }
- return json.Unmarshal([]byte(data), &rowids)
- })
- if err != nil {
- return nil, nil // Return empty if not found
- }
- return rowids, nil
- }
- // ClearIndex removes all entries for an index by scanning table and removing entries.
- func (m *TableManager) ClearIndex(indexName, tableName string, columns []string) error {
- rows, err := m.Select(tableName, nil)
- if err != nil {
- return err
- }
- for _, row := range rows {
- colValue := m.buildIndexValue(row, columns)
- key := m.indexEntryKey(indexName, colValue)
- m.pool.WithClient(func(c *KVClient) error {
- return c.Delete(key)
- })
- }
- return nil
- }
- // BuildIndex builds index entries for all existing rows in a table.
- func (m *TableManager) BuildIndex(indexName, tableName string, columns []string) error {
- rows, err := m.Select(tableName, nil)
- if err != nil {
- return err
- }
- for _, row := range rows {
- rowid, ok := row["_rowid_"].(float64)
- if !ok {
- continue
- }
- // Build composite key value for multi-column indexes
- colValue := m.buildIndexValue(row, columns)
- if err := m.AddIndexEntry(indexName, colValue, int64(rowid)); err != nil {
- return err
- }
- }
- return nil
- }
- // buildIndexValue creates the index key value from row columns.
- func (m *TableManager) buildIndexValue(row Row, columns []string) string {
- formatValue := func(v interface{}) string {
- switch val := v.(type) {
- case float64:
- // Check if it's actually an integer value
- if val == float64(int64(val)) {
- return fmt.Sprintf("%d", int64(val))
- }
- return fmt.Sprintf("%f", val)
- case int64:
- return fmt.Sprintf("%d", val)
- case int:
- return fmt.Sprintf("%d", val)
- default:
- return fmt.Sprintf("%v", val)
- }
- }
- if len(columns) == 1 {
- return formatValue(row[columns[0]])
- }
- // Multi-column index: concatenate values with separator
- var parts []string
- for _, col := range columns {
- parts = append(parts, formatValue(row[col]))
- }
- return strings.Join(parts, "\x00")
- }
- // SelectByIndex retrieves rows using an index lookup.
- func (m *TableManager) SelectByIndex(table, indexName string, colValue interface{}) ([]Row, error) {
- rowids, err := m.LookupIndex(indexName, colValue)
- if err != nil {
- return nil, err
- }
- // If no rowids found, return empty result
- if len(rowids) == 0 {
- return []Row{}, nil
- }
- schema, err := m.schema.GetSchema(table)
- if err != nil {
- return nil, err
- }
- // Check if primary key is INTEGER type (in which case rowid == pk)
- pkCol, _ := schema.GetColumn(schema.PrimaryKey)
- isPKInteger := pkCol != nil && isIntegerType(pkCol.Type)
- rows := make([]Row, 0, len(rowids))
- for _, rowid := range rowids {
- var row Row
- // For INTEGER PRIMARY KEY, the rowid IS the primary key
- if isPKInteger {
- row, err = m.GetByPK(table, fmt.Sprintf("%d", rowid))
- if err == nil {
- rows = append(rows, row)
- continue
- }
- }
- // For non-INTEGER primary keys or if PK lookup fails, look up by _rowid_
- allRows, _ := m.Select(table, func(r Row) bool {
- if rid, ok := r["_rowid_"].(float64); ok {
- return int64(rid) == rowid
- }
- if rid, ok := r["_rowid_"].(int64); ok {
- return rid == rowid
- }
- return false
- })
- if len(allRows) > 0 {
- rows = append(rows, allRows[0])
- }
- }
- return rows, nil
- }
|