package stardb import ( "context" "database/sql" "fmt" "reflect" "strings" ) // Orm maps query results to a struct or slice of structs // Usage: // // var user User // rows.Orm(&user) // single row // var users []User // rows.Orm(&users) // multiple rows func (r *StarRows) Orm(target interface{}) error { if !r.parsed { if err := r.parse(); err != nil { return err } } if target == nil { return ErrTargetNil } targetType := reflect.TypeOf(target) targetValue := reflect.ValueOf(target) if targetType.Kind() != reflect.Ptr { return ErrTargetNotPointer } if targetValue.IsNil() { return ErrTargetPointerNil } targetType = targetType.Elem() targetValue = targetValue.Elem() // Handle slice if targetValue.Kind() == reflect.Slice { elementType := targetType.Elem() result := reflect.MakeSlice(targetType, 0, r.Length()) if r.Length() == 0 { targetValue.Set(result) return nil } for i := 0; i < r.Length(); i++ { element := reflect.New(elementType) if err := r.setStructFieldsFromRow(element.Interface(), "db", i); err != nil { return err } result = reflect.Append(result, element.Elem()) } targetValue.Set(result) return nil } // Handle array if targetValue.Kind() == reflect.Array { elementType := targetType.Elem() if r.Length() == 0 { return nil } if r.Length() > targetValue.Len() { return fmt.Errorf("target array length %d is smaller than rows %d", targetValue.Len(), r.Length()) } for i := 0; i < r.Length(); i++ { element := reflect.New(elementType) if err := r.setStructFieldsFromRow(element.Interface(), "db", i); err != nil { return err } targetValue.Index(i).Set(element.Elem()) } return nil } // Handle single struct if r.Length() == 0 { return nil } return r.setStructFieldsFromRow(target, "db", 0) } func bindNamedArgs(args []interface{}, fieldValues map[string]interface{}) ([]interface{}, error) { processedArgs := make([]interface{}, len(args)) for i, arg := range args { str, ok := arg.(string) if !ok { processedArgs[i] = arg continue } if strings.HasPrefix(str, `\:`) { processedArgs[i] = str[1:] continue } if strings.HasPrefix(str, ":") { fieldName := str[1:] val, exists := fieldValues[fieldName] if !exists { return nil, wrapFieldNotFound(fieldName) } processedArgs[i] = val continue } processedArgs[i] = arg } return processedArgs, nil } // QueryX executes a query with named parameter binding // Usage: QueryX(&user, "SELECT * FROM users WHERE id = ?", ":id") func (s *StarDB) QueryX(target interface{}, query string, args ...interface{}) (*StarRows, error) { return s.queryX(nil, target, query, args...) } // QueryXContext executes a query with context and named parameter binding func (s *StarDB) QueryXContext(ctx context.Context, target interface{}, query string, args ...interface{}) (*StarRows, error) { return s.queryX(ctx, target, query, args...) } // queryX is the internal implementation func (s *StarDB) queryX(ctx context.Context, target interface{}, query string, args ...interface{}) (*StarRows, error) { fieldValues, err := getStructFieldValues(target, "db") if err != nil { return nil, err } processedArgs, err := bindNamedArgs(args, fieldValues) if err != nil { return nil, err } return s.query(ctx, query, processedArgs...) } // QueryXS executes queries for multiple structs func (s *StarDB) QueryXS(targets interface{}, query string, args ...interface{}) ([]*StarRows, error) { return s.queryXS(nil, targets, query, args...) } // QueryXSContext executes queries with context for multiple structs func (s *StarDB) QueryXSContext(ctx context.Context, targets interface{}, query string, args ...interface{}) ([]*StarRows, error) { return s.queryXS(ctx, targets, query, args...) } // queryXS is the internal implementation func (s *StarDB) queryXS(ctx context.Context, targets interface{}, query string, args ...interface{}) ([]*StarRows, error) { var results []*StarRows if targets == nil { return results, ErrTargetsNil } targetType := reflect.TypeOf(targets) targetValue := reflect.ValueOf(targets) if targetType.Kind() == reflect.Ptr { if targetValue.IsNil() { return results, ErrTargetsPointerNil } targetType = targetType.Elem() targetValue = targetValue.Elem() } if targetValue.Kind() == reflect.Slice || targetValue.Kind() == reflect.Array { results = make([]*StarRows, 0, targetValue.Len()) for i := 0; i < targetValue.Len(); i++ { result, err := s.queryX(ctx, targetValue.Index(i).Interface(), query, args...) if err != nil { return results, err } results = append(results, result) } } else { result, err := s.queryX(ctx, targets, query, args...) if err != nil { return results, err } results = append(results, result) } return results, nil } // ExecX executes a statement with named parameter binding func (s *StarDB) ExecX(target interface{}, query string, args ...interface{}) (sql.Result, error) { return s.execX(nil, target, query, args...) } // ExecXContext executes a statement with context and named parameter binding func (s *StarDB) ExecXContext(ctx context.Context, target interface{}, query string, args ...interface{}) (sql.Result, error) { return s.execX(ctx, target, query, args...) } // execX is the internal implementation func (s *StarDB) execX(ctx context.Context, target interface{}, query string, args ...interface{}) (sql.Result, error) { fieldValues, err := getStructFieldValues(target, "db") if err != nil { return nil, err } processedArgs, err := bindNamedArgs(args, fieldValues) if err != nil { return nil, err } return s.exec(ctx, query, processedArgs...) } // ExecXS executes statements for multiple structs func (s *StarDB) ExecXS(targets interface{}, query string, args ...interface{}) ([]sql.Result, error) { return s.execXS(nil, targets, query, args...) } // ExecXSContext executes statements with context for multiple structs func (s *StarDB) ExecXSContext(ctx context.Context, targets interface{}, query string, args ...interface{}) ([]sql.Result, error) { return s.execXS(ctx, targets, query, args...) } // execXS is the internal implementation func (s *StarDB) execXS(ctx context.Context, targets interface{}, query string, args ...interface{}) ([]sql.Result, error) { var results []sql.Result if targets == nil { return results, ErrTargetsNil } targetType := reflect.TypeOf(targets) targetValue := reflect.ValueOf(targets) if targetType.Kind() == reflect.Ptr { if targetValue.IsNil() { return results, ErrTargetsPointerNil } targetType = targetType.Elem() targetValue = targetValue.Elem() } if targetValue.Kind() == reflect.Slice || targetValue.Kind() == reflect.Array { results = make([]sql.Result, 0, targetValue.Len()) for i := 0; i < targetValue.Len(); i++ { result, err := s.execX(ctx, targetValue.Index(i).Interface(), query, args...) if err != nil { return results, err } results = append(results, result) } } else { result, err := s.execX(ctx, targets, query, args...) if err != nil { return results, err } results = append(results, result) } return results, nil } // Insert inserts a struct into the database // Usage: Insert(&user, "users", "id") // id is auto-increment func (s *StarDB) Insert(target interface{}, tableName string, autoIncrementFields ...string) (sql.Result, error) { return s.insert(nil, target, tableName, autoIncrementFields...) } // InsertContext inserts a struct with context func (s *StarDB) InsertContext(ctx context.Context, target interface{}, tableName string, autoIncrementFields ...string) (sql.Result, error) { return s.insert(ctx, target, tableName, autoIncrementFields...) } // insert is the internal implementation func (s *StarDB) insert(ctx context.Context, target interface{}, tableName string, autoIncrementFields ...string) (sql.Result, error) { query, params, err := buildInsertSQL(target, tableName, autoIncrementFields...) if err != nil { return nil, err } args := make([]interface{}, len(params)) for i, param := range params { args[i] = param } return s.execX(ctx, target, query, args...) } // Update updates a struct in the database // Usage: Update(&user, "users", "id") // id is primary key func (s *StarDB) Update(target interface{}, tableName string, primaryKeys ...string) (sql.Result, error) { return s.update(nil, target, tableName, primaryKeys...) } // UpdateContext updates a struct with context func (s *StarDB) UpdateContext(ctx context.Context, target interface{}, tableName string, primaryKeys ...string) (sql.Result, error) { return s.update(ctx, target, tableName, primaryKeys...) } // update is the internal implementation func (s *StarDB) update(ctx context.Context, target interface{}, tableName string, primaryKeys ...string) (sql.Result, error) { query, params, err := buildUpdateSQL(target, tableName, primaryKeys...) if err != nil { return nil, err } args := make([]interface{}, len(params)) for i, param := range params { args[i] = param } return s.execX(ctx, target, query, args...) } // buildInsertSQL builds an INSERT SQL statement func buildInsertSQL(target interface{}, tableName string, autoIncrementFields ...string) (string, []string, error) { if strings.TrimSpace(tableName) == "" { return "", []string{}, ErrTableNameEmpty } fieldNames, err := getStructFieldNames(target, "db") if err != nil { return "", []string{}, err } var columns []string var placeholders []string var params []string autoIncrementSet := make(map[string]struct{}, len(autoIncrementFields)) for _, autoField := range autoIncrementFields { autoIncrementSet[autoField] = struct{}{} } for _, fieldName := range fieldNames { // Skip auto-increment fields if _, isAutoIncrement := autoIncrementSet[fieldName]; isAutoIncrement { continue } columns = append(columns, fieldName) placeholders = append(placeholders, "?") params = append(params, ":"+fieldName) } if len(columns) == 0 { return "", []string{}, ErrNoInsertColumns } query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", tableName, strings.Join(columns, ", "), strings.Join(placeholders, ", ")) return query, params, nil } // buildUpdateSQL builds an UPDATE SQL statement func buildUpdateSQL(target interface{}, tableName string, primaryKeys ...string) (string, []string, error) { if strings.TrimSpace(tableName) == "" { return "", []string{}, ErrTableNameEmpty } fieldNames, err := getStructFieldNames(target, "db") if err != nil { return "", []string{}, err } if len(primaryKeys) == 0 { return "", []string{}, ErrPrimaryKeyRequired } primaryKeySet := make(map[string]struct{}, len(primaryKeys)) for _, pk := range primaryKeys { if pk == "" { return "", []string{}, ErrPrimaryKeyEmpty } primaryKeySet[pk] = struct{}{} } var setClauses []string var params []string // Build SET clause for _, fieldName := range fieldNames { if _, isPrimaryKey := primaryKeySet[fieldName]; isPrimaryKey { continue } setClauses = append(setClauses, fmt.Sprintf("%s = ?", fieldName)) params = append(params, ":"+fieldName) } if len(setClauses) == 0 { return "", []string{}, ErrNoUpdateFields } // Build WHERE clause var whereClauses []string for _, pk := range primaryKeys { whereClauses = append(whereClauses, fmt.Sprintf("%s = ?", pk)) params = append(params, ":"+pk) } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", tableName, strings.Join(setClauses, ", "), strings.Join(whereClauses, " AND ")) return query, params, nil } // Transaction ORM methods // QueryX executes a query with named parameter binding in a transaction func (t *StarTx) QueryX(target interface{}, query string, args ...interface{}) (*StarRows, error) { return t.queryX(nil, target, query, args...) } // QueryXContext executes a query with context in a transaction func (t *StarTx) QueryXContext(ctx context.Context, target interface{}, query string, args ...interface{}) (*StarRows, error) { return t.queryX(ctx, target, query, args...) } // queryX is the internal implementation func (t *StarTx) queryX(ctx context.Context, target interface{}, query string, args ...interface{}) (*StarRows, error) { fieldValues, err := getStructFieldValues(target, "db") if err != nil { return nil, err } processedArgs, err := bindNamedArgs(args, fieldValues) if err != nil { return nil, err } return t.query(ctx, query, processedArgs...) } // QueryXS executes queries for multiple structs in a transaction func (t *StarTx) QueryXS(targets interface{}, query string, args ...interface{}) ([]*StarRows, error) { return t.queryXS(nil, targets, query, args...) } // QueryXSContext executes queries with context for multiple structs in a transaction func (t *StarTx) QueryXSContext(ctx context.Context, targets interface{}, query string, args ...interface{}) ([]*StarRows, error) { return t.queryXS(ctx, targets, query, args...) } // queryXS is the internal implementation func (t *StarTx) queryXS(ctx context.Context, targets interface{}, query string, args ...interface{}) ([]*StarRows, error) { var results []*StarRows if targets == nil { return results, ErrTargetsNil } targetType := reflect.TypeOf(targets) targetValue := reflect.ValueOf(targets) if targetType.Kind() == reflect.Ptr { if targetValue.IsNil() { return results, ErrTargetsPointerNil } targetType = targetType.Elem() targetValue = targetValue.Elem() } if targetValue.Kind() == reflect.Slice || targetValue.Kind() == reflect.Array { results = make([]*StarRows, 0, targetValue.Len()) for i := 0; i < targetValue.Len(); i++ { result, err := t.queryX(ctx, targetValue.Index(i).Interface(), query, args...) if err != nil { return results, err } results = append(results, result) } } else { result, err := t.queryX(ctx, targets, query, args...) if err != nil { return results, err } results = append(results, result) } return results, nil } // ExecX executes a statement with named parameter binding in a transaction func (t *StarTx) ExecX(target interface{}, query string, args ...interface{}) (sql.Result, error) { return t.execX(nil, target, query, args...) } // ExecXContext executes a statement with context in a transaction func (t *StarTx) ExecXContext(ctx context.Context, target interface{}, query string, args ...interface{}) (sql.Result, error) { return t.execX(ctx, target, query, args...) } // execX is the internal implementation func (t *StarTx) execX(ctx context.Context, target interface{}, query string, args ...interface{}) (sql.Result, error) { fieldValues, err := getStructFieldValues(target, "db") if err != nil { return nil, err } processedArgs, err := bindNamedArgs(args, fieldValues) if err != nil { return nil, err } return t.exec(ctx, query, processedArgs...) } // ExecXS executes statements for multiple structs in a transaction func (t *StarTx) ExecXS(targets interface{}, query string, args ...interface{}) ([]sql.Result, error) { return t.execXS(nil, targets, query, args...) } // ExecXSContext executes statements with context for multiple structs in a transaction func (t *StarTx) ExecXSContext(ctx context.Context, targets interface{}, query string, args ...interface{}) ([]sql.Result, error) { return t.execXS(ctx, targets, query, args...) } // execXS is the internal implementation func (t *StarTx) execXS(ctx context.Context, targets interface{}, query string, args ...interface{}) ([]sql.Result, error) { var results []sql.Result if targets == nil { return results, ErrTargetsNil } targetType := reflect.TypeOf(targets) targetValue := reflect.ValueOf(targets) if targetType.Kind() == reflect.Ptr { if targetValue.IsNil() { return results, ErrTargetsPointerNil } targetType = targetType.Elem() targetValue = targetValue.Elem() } if targetValue.Kind() == reflect.Slice || targetValue.Kind() == reflect.Array { results = make([]sql.Result, 0, targetValue.Len()) for i := 0; i < targetValue.Len(); i++ { result, err := t.execX(ctx, targetValue.Index(i).Interface(), query, args...) if err != nil { return results, err } results = append(results, result) } } else { result, err := t.execX(ctx, targets, query, args...) if err != nil { return results, err } results = append(results, result) } return results, nil } // Insert inserts a struct in a transaction func (t *StarTx) Insert(target interface{}, tableName string, autoIncrementFields ...string) (sql.Result, error) { return t.insert(nil, target, tableName, autoIncrementFields...) } // InsertContext inserts a struct with context in a transaction func (t *StarTx) InsertContext(ctx context.Context, target interface{}, tableName string, autoIncrementFields ...string) (sql.Result, error) { return t.insert(ctx, target, tableName, autoIncrementFields...) } // insert is the internal implementation func (t *StarTx) insert(ctx context.Context, target interface{}, tableName string, autoIncrementFields ...string) (sql.Result, error) { query, params, err := buildInsertSQL(target, tableName, autoIncrementFields...) if err != nil { return nil, err } args := make([]interface{}, len(params)) for i, param := range params { args[i] = param } return t.execX(ctx, target, query, args...) } // Update updates a struct in a transaction func (t *StarTx) Update(target interface{}, tableName string, primaryKeys ...string) (sql.Result, error) { return t.update(nil, target, tableName, primaryKeys...) } // UpdateContext updates a struct with context in a transaction func (t *StarTx) UpdateContext(ctx context.Context, target interface{}, tableName string, primaryKeys ...string) (sql.Result, error) { return t.update(ctx, target, tableName, primaryKeys...) } // update is the internal implementation func (t *StarTx) update(ctx context.Context, target interface{}, tableName string, primaryKeys ...string) (sql.Result, error) { query, params, err := buildUpdateSQL(target, tableName, primaryKeys...) if err != nil { return nil, err } args := make([]interface{}, len(params)) for i, param := range params { args[i] = param } return t.execX(ctx, target, query, args...) }