package tableDB import ( "fmt" "strings" "apigo.cc/go/cast" "apigo.cc/go/db" "apigo.cc/go/id" ) // Table provides an AI-friendly interface for interacting with structured data or schema. type Table struct { Name string userID string db *db.DB app *TableDB isMeta bool // True if Name ends with ":Schema" constraint map[string]any } // NewTable creates a new Table instance. Handles ":Schema" and ":Field" suffix. func NewTable(name string, app *TableDB) *Table { isMeta := false actualName := name var constraint map[string]any if strings.HasSuffix(name, ":Schema") { isMeta = true actualName = strings.TrimSuffix(name, ":Schema") } else if strings.HasSuffix(name, ":Field") { tableName := strings.TrimSuffix(name, ":Field") actualName = "_Field" // lookup table_id res := app.base.Query("SELECT id FROM `_Table` WHERE name = ? LIMIT 1", tableName) if res.Error == nil { rec := res.MapOnR1() if len(rec) > 0 { constraint = map[string]any{"tableId": cast.String(rec["id"])} } } } return &Table{ Name: actualName, userID: app.userID, db: app.base, app: app, isMeta: isMeta, constraint: constraint, } } func (t *Table) checkAuth(id string) error { if t.userID == "_system" || !strings.HasPrefix(t.Name, "_") { return nil } if t.Name == "_Policy" || t.Name == "_Backup" { return fmt.Errorf("permission denied for %s", t.Name) } // Check existing record res := t.db.Query(fmt.Sprintf("SELECT creator FROM `%s` WHERE id = ?", t.Name), id) rec := res.MapOnR1() if len(rec) > 0 { if cast.String(rec["creator"]) == t.userID { return nil } // Check _Policy pol := t.db.Query("SELECT 1 FROM _Policy WHERE subject = ? AND resource = ? AND effect = 'allow' LIMIT 1", t.userID, id) if len(pol.MapOnR1()) > 0 { return nil } return fmt.Errorf("permission denied for %s record %s", t.Name, id) } return nil } func (t *Table) appendAuthAndConstraint(whereStr string, args []any) (string, []any, error) { if t.constraint != nil { for k, v := range t.constraint { if whereStr != "" { whereStr += fmt.Sprintf(" AND %s = ?", k) } else { whereStr = fmt.Sprintf("%s = ?", k) } args = append(args, v) } } if t.userID != "_system" && strings.HasPrefix(t.Name, "_") { if t.Name == "_Policy" || t.Name == "_Backup" { return "", nil, fmt.Errorf("permission denied for %s", t.Name) } authWhere := fmt.Sprintf("(creator = '%s' OR id IN (SELECT resource FROM _Policy WHERE subject = '%s' AND effect = 'allow'))", t.userID, t.userID) if whereStr != "" { whereStr = "(" + whereStr + ") AND " + authWhere } else { whereStr = authWhere } } return whereStr, args, nil } func (t *Table) reconstructAndSyncSchema() error { tables := t.db.Query("SELECT * FROM `_Table`").MapResults() fields := t.db.Query("SELECT * FROM `_Field` ORDER BY tableId").MapResults() fieldMap := make(map[string][]map[string]any) for _, f := range fields { tid := cast.String(f["tableId"]) fieldMap[tid] = append(fieldMap[tid], f) } var sb strings.Builder for _, tbl := range tables { name := cast.String(tbl["name"]) if name == "" { continue } memo := cast.String(tbl["memo"]) sb.WriteString(name + " SD") if memo != "" { sb.WriteString(" //" + strings.ReplaceAll(memo, "\n", " ")) } sb.WriteString("\n") tid := cast.String(tbl["id"]) for _, f := range fieldMap[tid] { fname := cast.String(f["name"]) ftype := cast.String(f["type"]) if ftype == "" { ftype = "v255" } isIndex := cast.Int(f["isIndex"]) == 1 fmemo := cast.String(f["memo"]) sb.WriteString(" " + fname + " " + ftype) if isIndex { sb.WriteString(" I") } if fmemo != "" { sb.WriteString(" //" + strings.ReplaceAll(fmemo, "\n", " ")) } sb.WriteString("\n") } sb.WriteString("\n") } return t.db.Sync(sb.String()) } // Set performs an upsert. func (t *Table) Set(data any) error { if t.userID == "" { return fmt.Errorf("no permission") } record := make(map[string]any) j, _ := cast.ToJSON(data) _ = cast.UnmarshalJSON([]byte(j), &record) if t.isMeta { dsl, ok := record["dsl"].(string) if !ok { return fmt.Errorf("schema dsl is required") } return t.app.SyncSchema(dsl) } if t.constraint != nil { for k, v := range t.constraint { record[k] = v } } if t.app.Hooks.OnUpdatingRow != nil && t.Name != "_Table" && t.Name != "_Field" { if err := t.app.Hooks.OnUpdatingRow(t.Name, record); err != nil { return err } } idVal := record["id"] var isInsert bool if idVal == nil || cast.String(idVal) == "" { record["id"] = id.MakeID(10) isInsert = true } else { idStr := cast.String(idVal) res := t.db.Query(fmt.Sprintf("SELECT id FROM `%s` WHERE id = ? LIMIT 1", t.Name), idStr) rec := res.MapOnR1() if rec != nil && len(rec) > 0 { if err := t.checkAuth(idStr); err != nil { return err } isInsert = false } else { isInsert = true } } var err error if isInsert { if t.userID != "_system" && strings.HasPrefix(t.Name, "_") { if t.Name == "_Policy" || t.Name == "_Backup" { return fmt.Errorf("permission denied for %s", t.Name) } record["creator"] = t.userID } err = t.db.Insert(t.Name, record).Error } else { err = t.db.Update(t.Name, record, "id = ?", cast.String(record["id"])).Error } if err == nil { if t.Name == "_Table" { _ = t.reconstructAndSyncSchema() if isInsert && t.app.Hooks.OnCreatedTable != nil { t.app.Hooks.OnCreatedTable(cast.String(record["name"]), record) } } else if t.Name == "_Field" { _ = t.reconstructAndSyncSchema() if t.app.Hooks.OnUpdatedField != nil { t.app.Hooks.OnUpdatedField(cast.String(record["tableId"]), cast.String(record["name"]), record) } } else { if t.app.Hooks.OnUpdatedRows != nil { t.app.Hooks.OnUpdatedRows(t.Name, 1) } } } return err } // Get retrieves a single record. func (t *Table) Get(id string) (map[string]any, error) { if t.isMeta { return map[string]any{"name": t.Name, "type": "table"}, nil } if err := t.checkAuth(id); err != nil { return nil, err } query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? LIMIT 1", t.Name) res := t.db.Query(query, id) if res.Error != nil { return nil, res.Error } record := res.MapOnR1() if len(record) == 0 { return nil, nil } return record, nil } // Remove deletes a record. func (t *Table) Remove(id string) error { if t.isMeta { return fmt.Errorf("schema removal not allowed via Table API") } if err := t.checkAuth(id); err != nil { return err } var record map[string]any if t.Name == "_Table" || t.Name == "_Field" { res := t.db.Query(fmt.Sprintf("SELECT * FROM `%s` WHERE id = ?", t.Name), id) record = res.MapOnR1() } res := t.db.Delete(t.Name, "id = ?", id) if res.Error == nil { if t.Name == "_Table" { _ = t.reconstructAndSyncSchema() if record != nil && t.app.Hooks.OnRemovedTable != nil { t.app.Hooks.OnRemovedTable(cast.String(record["name"])) } } else if t.Name == "_Field" { _ = t.reconstructAndSyncSchema() if record != nil && t.app.Hooks.OnRemovedField != nil { t.app.Hooks.OnRemovedField(cast.String(record["tableId"]), cast.String(record["name"])) } } else { if t.app.Hooks.OnRemovedRows != nil { t.app.Hooks.OnRemovedRows(t.Name, []string{id}) } } } return res.Error } // List retrieves multiple records. func (t *Table) List(where any, args ...any) ([]map[string]any, error) { if t.isMeta { return nil, fmt.Errorf("list not supported on schema tables") } query := fmt.Sprintf("SELECT * FROM `%s` ", t.Name) whereStr := "" if where != nil { switch v := where.(type) { case string: whereStr = v case map[string]any: whereStr, args = buildWhere(v) } } var err error whereStr, args, err = t.appendAuthAndConstraint(whereStr, args) if err != nil { return nil, err } if whereStr != "" { query += " WHERE " + whereStr } res := t.db.Query(query, args...) if res.Error != nil { return nil, res.Error } return res.MapResults(), nil } // Count returns the number of records. func (t *Table) Count(where any, args ...any) (int64, error) { query := fmt.Sprintf("SELECT COUNT(*) FROM `%s` ", t.Name) whereStr := "" if where != nil { switch v := where.(type) { case string: whereStr = v case map[string]any: w, a := buildWhere(v) whereStr = w args = a } } var err error whereStr, args, err = t.appendAuthAndConstraint(whereStr, args) if err != nil { return 0, err } if whereStr != "" { query += " WHERE " + whereStr } res := t.db.Query(query, args...) if res.Error != nil { return 0, res.Error } return res.IntOnR1C1(), nil } // CountBy returns counts grouped by a field. func (t *Table) CountBy(field string) (map[any]int64, error) { query := fmt.Sprintf("SELECT `%s`, COUNT(*) as cnt FROM `%s` ", field, t.Name) whereStr, args, err := t.appendAuthAndConstraint("", nil) if err != nil { return nil, err } if whereStr != "" { query += " WHERE " + whereStr } query += fmt.Sprintf(" GROUP BY `%s` ", field) res := t.db.Query(query, args...) if res.Error != nil { return nil, res.Error } rows := res.MapResults() result := make(map[any]int64) for _, row := range rows { result[row[field]] = cast.Int64(row["cnt"]) } return result, nil } // Fields returns field metadata. func (t *Table) Fields() ([]FieldSchema, error) { tid := "" tableRecord := GlobalCache.GetTable(t.Name) if tableRecord != nil { tid = cast.String(tableRecord["id"]) } else { return nil, fmt.Errorf("table metadata not found in cache: %s", t.Name) } return GlobalCache.GetFields(tid), nil }