docs: Add CLAUDE.md with codebase guidance

Create comprehensive documentation for future Claude Code instances working in this repository, including:
- Development commands for testing, building, and code quality
- Core architecture overview of the SQL query builder system
- Directory structure and component explanations
- Testing patterns and conventions
- Key dependencies and their purposes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-27 15:47:28 +08:00
co-authored by Claude
parent 764eccfafd
commit 304d553b3c
113 changed files with 914 additions and 432 deletions
+83 -8
View File
@@ -2,11 +2,12 @@ package db
import (
"fmt"
"reflect"
"git.fsdpf.net/go/db/v2/exec"
"git.fsdpf.net/go/db/v2/exp"
"git.fsdpf.net/go/db/v2/internal/errors"
"git.fsdpf.net/go/db/v2/internal/sb"
"git.fsdpf.net/go/db/exec"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/db/internal/errors"
"git.fsdpf.net/go/db/internal/sb"
)
type InsertDataset struct {
@@ -15,6 +16,7 @@ type InsertDataset struct {
isPrepared prepared
queryFactory exec.QueryFactory
err error
hooks exec.Hooks
}
var ErrUnsupportedIntoType = errors.New("unsupported table type, a string or identifier expression is required")
@@ -88,6 +90,7 @@ func (id *InsertDataset) copy(clauses exp.InsertClauses) *InsertDataset {
isPrepared: id.isPrepared,
queryFactory: id.queryFactory,
err: id.err,
hooks: id.hooks,
}
}
@@ -170,9 +173,65 @@ func (id *InsertDataset) ClearVals() *InsertDataset {
return id.copy(id.clauses.SetVals(nil))
}
// Insert rows. Rows can be a map, db.Record or struct. See examples.
// Insert rows. Rows can be a map, db.Record, struct, slice or array. See examples.
func (id *InsertDataset) Rows(rows ...interface{}) *InsertDataset {
return id.copy(id.clauses.SetRows(rows))
// If a single argument is a slice or array, expand it as rows
if len(rows) == 1 {
val := reflect.ValueOf(rows[0])
kind := val.Kind()
if kind == reflect.Slice || kind == reflect.Array {
expanded := make([]interface{}, val.Len())
for i := 0; i < val.Len(); i++ {
expanded[i] = val.Index(i).Interface()
}
// 递归调用 Rows 展开切片或数组
return id.Rows(expanded...)
}
}
converted := make([]interface{}, len(rows))
for i, row := range rows {
switch v := row.(type) {
case map[string]interface{}:
converted[i] = v
case Record:
converted[i] = map[string]interface{}(v)
default:
val := reflect.ValueOf(row)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() == reflect.Slice || val.Kind() == reflect.Array {
panic("Rows: nested slice/array not supported, pass as top-level argument")
}
if val.Kind() != reflect.Struct {
panic("Rows: unsupported row type, must be map, Record, struct, slice or array")
}
result := make(map[string]interface{})
typ := val.Type()
for j := 0; j < val.NumField(); j++ {
field := typ.Field(j)
// Skip unexported fields
if field.PkgPath != "" {
continue
}
// Use db tag if present, otherwise use field name
key := field.Name
if dbTag, ok := field.Tag.Lookup("db"); ok && dbTag != "" {
if dbTag == "-" {
continue // Skip fields with db tag "-"
}
key = dbTag
}
result[key] = val.Field(j).Interface()
}
converted[i] = result
}
}
return id.copy(id.clauses.SetRows(converted))
}
// Clears the rows for this insert dataset. See examples.
@@ -259,8 +318,24 @@ func (id *InsertDataset) ReturnsColumns() bool {
// Generates the INSERT sql, and returns an QueryExecutor struct with the sql set to the INSERT statement
//
// db.Insert("test").Rows(Record{"name":"Bob"}).Executor().Exec()
func (id *InsertDataset) Executor() exec.QueryExecutor {
return id.queryFactory.FromSQLBuilder(id.insertSQLBuilder())
func (id *InsertDataset) Executor() (executor exec.QueryExecutor) {
if id.hooks != nil {
id.SetError(id.hooks.Before(id))
}
executor = id.queryFactory.FromSQLBuilder(id.insertSQLBuilder())
if id.hooks != nil {
executor.Hook(func(result interface{}) error {
return id.hooks.After(id, result)
})
}
return executor
}
func (id *InsertDataset) WithHook(hooks exec.Hooks) *InsertDataset {
if id.hooks == nil {
id.hooks = hooks
}
return id
}
func (id *InsertDataset) insertSQLBuilder() sb.SQLBuilder {