Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
67fb959
refactor: 💡 optimize
leisurelicht Jan 20, 2026
ff9c0bb
test: 💍 add bench test for handler
leisurelicht Jan 23, 2026
5439192
test: 💍 add and unify wrapWithBackticks benchmark
leisurelicht Jan 23, 2026
70f527d
style: 💄 unify wrapWithBackticks comment language
leisurelicht Jan 23, 2026
59db9eb
chore(cursor): add worktree setup configuration
leisurelicht Jan 26, 2026
f284408
docs(cursor): add commit command with auto-splitting
leisurelicht Jan 26, 2026
2d7a9ef
style(cursor): remove spaces after emojis in types list
leisurelicht Jan 26, 2026
a9799b6
docs(cursor): clarify emoji type in commit format spec
leisurelicht Jan 26, 2026
558408c
docs(cursor): add commit1 command with detailed workflow
leisurelicht Jan 26, 2026
799366b
chore(cursor): remove commit1 command file
leisurelicht Jan 26, 2026
98a48bd
docs(cursor): update commit format and add emoji reminder
leisurelicht Jan 26, 2026
8369def
docs(cursor): simplify commit command docs for ai automator
leisurelicht Jan 26, 2026
0ce8efa
✏️ docs(cursor): require emojis in commit message format
leisurelicht Jan 26, 2026
c85411e
✏️ docs(cursor): optimize ai commit helper wording
leisurelicht Jan 26, 2026
3fe500a
✏️ docs(cursor): add code-review skill for Go projects
leisurelicht Jan 28, 2026
cf24615
💡 refactor(queryset): simplify contains operator branching
leisurelicht Jan 29, 2026
726b088
💍 test(queryset): fix duplicate names and error assertions
leisurelicht Jan 29, 2026
2b084e8
💡 refactor(queryset): fix typo and remove redundant variable
leisurelicht Jan 29, 2026
0046f4e
💡 refactor(handler): simplify methods and reduce nesting
leisurelicht Jan 29, 2026
fc12622
✏️ docs(cursor): update code-review performance guidance
leisurelicht Jan 29, 2026
44da3cf
🐛 fix(handler): return nil for empty results in FindAllModel
leisurelicht Jan 30, 2026
a07dcd5
🐛 fix(test): fix transaction rollback and assertion logic errors
leisurelicht Jan 30, 2026
a28edb5
💄 style: improve code comments for clarity
leisurelicht Jan 30, 2026
3dabfef
🤖 chore(skills): remove code-review skill
leisurelicht Feb 2, 2026
619a3c7
💍 test(handler): refactor error assertions and add table-driven tests
leisurelicht Feb 4, 2026
902a97e
💍 test(handler): add missing unsupported operation test cases
leisurelicht Feb 5, 2026
625733a
💡 refactor(test): remove duplicate test functions
leisurelicht Feb 5, 2026
f172f59
🐛 fix(handler): add empty update validation and error constants
leisurelicht Feb 5, 2026
ee6a62e
💡 refactor(test): use error constants in test assertions
leisurelicht Feb 5, 2026
bce30c4
💍 test(utils): expand model utility coverage
leisurelicht Feb 5, 2026
54a24a8
💍 test(handler): broaden mysql handler behaviour and error coverage
leisurelicht Feb 5, 2026
d813832
🐛 fix(handler): pass where args correctly and extend coverage
leisurelicht Feb 5, 2026
c3f55f9
💍 test(handler): cover transactional update and reset semantics
leisurelicht Feb 5, 2026
cf60d57
🐛 fix(queryset): tighten where arg validation and extend coverage
leisurelicht Feb 5, 2026
a33b879
🐛 fix(handler): simplify error handling and cover db error paths
leisurelicht Feb 5, 2026
9cbebb1
🐛 fix(clickhouse): stop returning ErrNotFound on empty FindAll
leisurelicht Feb 28, 2026
e16a6d7
💡 refactor(config): ensure init before Get/SetLevel via ensureInit
leisurelicht Feb 28, 2026
a82f3a3
🐛 fix(mysql): remove invalid logx LogConf usage
leisurelicht Feb 28, 2026
c004d0b
💡 refactor(go-zero): rewrite BulkInsert with explicit query builder
leisurelicht Mar 2, 2026
dc8ba2b
💍 test(handler): add batch create with time.Now fields
leisurelicht Mar 2, 2026
af569a0
💡 refactor(handler): replace deprecated reflect.Ptr with reflect.Pointer
leisurelicht Mar 3, 2026
7f08d87
🎸 feat(select): support select for map queries
leisurelicht Mar 3, 2026
b12f662
🤖 chore(git): stop tracking cursor files
leisurelicht Mar 9, 2026
446383b
💡 refactor(handler): simplify FindAllModel return
leisurelicht Mar 9, 2026
dd8ae8f
🐛 fix(handler): spread Having args
leisurelicht Mar 12, 2026
4e2e29b
🐛 fix(go-zero): robust VALUES detection in buildBulkInsertQuery
leisurelicht Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .cursor/commands/commit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# AI Git Automator: Atomic Commits (Optimized)

**Goal:** Auto-split independent changes into **well-structured Conventional Commits** with mandatory emojis.

## 1. Analysis & Grouping (Mandatory)

* **Ops:** Run `git status` and `git diff`.
* **Logic:** Cluster files by **logical concern** (type, scope, or intent).
* **Constraint:** **MUST SPLIT** commits if changes have different types (e.g., `feat` vs `fix`), different scopes, or can be reverted independently. **Do NOT proceed** without full understanding.

## 2. Execution Loop

*Repeat until worktree clean:*

1. **Stage:** `git add <paths>` (Select **ONE** coherent concern only).
2. **Commit:** Execute the following command immediately (Verify atomicity internally):
```bash
git commit -m "<emoji> <type>(<scope>): <summary>

Why:
- [Briefly explain the underlying reason/problem]
What:
- [Summary of technical changes]
Files:
- [Key files/dirs, omit trivial]"

```
## 3. Final Output (Once Finished)

After all commits are done:

Print a summary table or list: [Hash] | [Emoji] [Title] | [Files Changed].

## 4. Standards & Rules

* **Title:** ≤ 72 chars, imperative mood, focus on **WHY**.
* **Emoji Map:** 🎸feat, 🐛fix, ✏️docs, 💄style, 💡refactor, ⚡️perf, 💍test, 🎡ci, 🤖chore.
* **Format:** `<emoji> <type>(<scope>): <summary>` (e.g., `🎸 feat(auth): add google oauth`).
* **Breaking Change:** Add `!` after type (e.g., `feat!(api): ...`).
* **Order:** `chore/ci` > `refactor` > `feat` > `fix` > `test` > `docs`.
* **Safety:** No empty commits; **No config changes**; **No `git push`**.
Comment thread
cursor[bot] marked this conversation as resolved.
5 changes: 5 additions & 0 deletions .cursor/worktrees.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"setup-worktree": [
"npm install"
]
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ go.work.sum
.vscode

.history

.cursor
2 changes: 1 addition & 1 deletion exposed.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var (
ToOR = queryset.ToOR
)

// 泛型包装:在本包重新导出 EachOR
// EachOR re-exports the generic EachOR function from queryset package
func EachOR[T Cond | AND | OR](conditions T) T {
return queryset.EachOR(conditions)
}
Expand Down
126 changes: 72 additions & 54 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,22 @@ const (
)

const (
SelectColumsValidateError = "select columns validate error: %s"
SelectColumnsTypeError = "select type should be string or string slice"
OrderByColumnsValidateError = "orderBy columns validate error: [%s] not exist"
OrderByColumnsTypeError = "orderBy type should be string or string slice"
GroupByColumnsValidateError = "groupBy columns validate error: %s"
GroupByColumnsTypeError = "groupBy type should be string or string slice"
CreateDataTypeError = "create data type is wrong, should not be [%s]"
DataEmptyError = "data is empty"
UpdateColumnNotExistError = "update column [%s] not exist"
ColumnNotExistError = "column [%s] not exist"
MustBeCalledError = "[%s] must be called after [%s]"
UnsupportedControllerError = "[%s] not supported for %s"
ModelTypeNotStructError = "model must be a pointer to struct"
ModelTypeNotSliceError = "model must be a pointer to slice"
SelectColumsValidateError = "select columns validate error: %s"
SelectColumnsTypeError = "select type should be string or string slice"
SelectQualifiedAsteriskError = "qualified wildcard select is not supported: %s"
SelectAliasNotSupportedError = "select alias is not supported for %s, please use %s"
OrderByColumnsValidateError = "orderBy columns validate error: [%s] not exist"
OrderByColumnsTypeError = "orderBy type should be string or string slice"
GroupByColumnsValidateError = "groupBy columns validate error: %s"
GroupByColumnsTypeError = "groupBy type should be string or string slice"
CreateDataTypeError = "create data type is wrong, should not be [%s]"
DataEmptyError = "data is empty"
UpdateColumnNotExistError = "update column [%s] not exist"
ColumnNotExistError = "column [%s] not exist"
MustBeCalledError = "[%s] must be called after [%s]"
UnsupportedControllerError = "[%s] not supported for %s"
)

type controllerCall struct {
Expand Down Expand Up @@ -172,10 +176,7 @@ func (m *Impl) setError(format string, a ...any) {
}

func (m *Impl) haveError() error {
if err := m.qs.Error(); err != nil {
return err
}
return nil
return m.qs.Error()
}

// preCheck checks if any unsupported methods have been called for the given operation.
Expand Down Expand Up @@ -275,7 +276,7 @@ func (m *Impl) Exclude(exclude ...any) Controller {
func (m *Impl) Where(cond string, args ...any) Controller {
m.setCalled(ctlWhere)

m.qs.WhereToSQL(cond, args)
m.qs.WhereToSQL(cond, args...)

return m
}
Expand All @@ -292,6 +293,10 @@ func (m *Impl) Select(selects any) Controller {
if sel == "" {
return m
}
if hasQualifiedWildcardSelect(sel) {
m.setError(SelectQualifiedAsteriskError, sel)
return m
}
m.qs.StrSelectToSQL(sel)
case []string:
if len(sel) == 0 {
Expand Down Expand Up @@ -347,7 +352,7 @@ func (m *Impl) OrderBy(orderBy any) Controller {
if len(orderByVal) == 0 {
return m
}
unknownColumns := []string{}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having args not spread like fixed Where method

High Severity

The Having method calls m.qs.HavingToSQL(having, args) without the spread operator ..., while the identical pattern in Where was fixed in this commit to use args.... Since HavingToSQL accepts variadic ...any, passing args (a []any) without spreading wraps it in an extra layer, resulting in []any{[]any{actual_args}} instead of []any{actual_args}. This causes SQL parameter binding to fail at runtime when Having is used with arguments.

Additional Locations (1)

Fix in Cursor Fix in Web

var unknownColumns []string
for _, by := range orderByVal {
if by == "" {
continue
Expand Down Expand Up @@ -413,7 +418,7 @@ func (m *Impl) GroupBy(groupBy any) Controller {
// Having adds a HAVING clause to the query.
func (m *Impl) Having(having string, args ...any) Controller {
m.setCalled(ctlHaving)
m.qs.HavingToSQL(having, args)
m.qs.HavingToSQL(having, args...)
return m
}

Expand Down Expand Up @@ -477,7 +482,7 @@ func (m *Impl) Create(data any) (idOrNum int64, err error) {
return m.bulkCreate(d)
default:
v := reflect.ValueOf(data)
if v.Kind() == reflect.Ptr {
if v.Kind() == reflect.Pointer {
v = v.Elem()
}
if v.Kind() == reflect.Struct {
Expand Down Expand Up @@ -506,6 +511,10 @@ func (m *Impl) Remove() (num int64, err error) {
}

func (m *Impl) update(data map[string]any) (num int64, err error) {
if len(data) == 0 {
return 0, errors.New("update " + DataEmptyError)
}

var (
args []any
updateRows []string
Expand Down Expand Up @@ -553,7 +562,7 @@ func (m *Impl) Count() (num int64, err error) {
}

func (m *Impl) findOne() (result map[string]any, err error) {
query, args := m.buildQuery(m.fieldRows)
query, args := m.buildQuery(m.qs.GetSelectSQL())
query += " LIMIT 1"

res := deepCopyModelPtrStructure(m.modelPtr)
Expand All @@ -562,20 +571,29 @@ func (m *Impl) findOne() (result map[string]any, err error) {

switch {
case err == nil:
return modelStruct2Map(res, m.operator.GetDBTag()), nil
result = modelStruct2Map(res, m.operator.GetDBTag())
case errors.Is(err, ErrNotFound):
return map[string]any{}, nil
default:
return map[string]any{}, err
}

if m.hasCalled(ctlSelect) {
result = filterBySelectColumns(result, m.qs.GetSelectSQL())
}

return result, nil
}

// FindOne retrieves a single record matching the current query set into a map.
// It returns the data as a map, or an error if the operation fails.
func (m *Impl) FindOne() (result map[string]any, err error) {
if err = m.preCheck("FindOne", ctlSelect, ctlHaving); err != nil {
if err = m.preCheck("FindOne", ctlHaving); err != nil {
return result, err
}
if m.hasCalled(ctlSelect) && hasSelectAlias(m.qs.GetSelectSQL()) {
return result, fmt.Errorf(SelectAliasNotSupportedError, "FindOne", "FindOneModel")
}

return m.findOne()
}
Expand All @@ -588,8 +606,8 @@ func (m *Impl) FindOneModel(modelPtr any) (err error) {
}

rv := reflect.ValueOf(modelPtr)
if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Struct {
return fmt.Errorf("model must be a pointer to struct")
if rv.Kind() != reflect.Pointer || rv.Elem().Kind() != reflect.Struct {
return fmt.Errorf(ModelTypeNotStructError)
}

query, args := m.buildQuery(m.qs.GetSelectSQL())
Expand All @@ -601,25 +619,33 @@ func (m *Impl) FindOneModel(modelPtr any) (err error) {
// FindAll retrieves all records matching the current query set into a slice of maps.
// It returns the data as a slice of maps, or an error if the operation fails.
func (m *Impl) FindAll() (result []map[string]any, err error) {
if err = m.preCheck("FindAll", ctlSelect, ctlHaving); err != nil {
if err = m.preCheck("FindAll", ctlHaving); err != nil {
return result, err
}
if m.hasCalled(ctlSelect) && hasSelectAlias(m.qs.GetSelectSQL()) {
return result, fmt.Errorf(SelectAliasNotSupportedError, "FindAll", "FindAllModel")
}

query, args := m.buildQuery(m.fieldRows)
query, args := m.buildQuery(m.qs.GetSelectSQL())
query += m.qs.GetLimitSQL()

res := deepCopyModelPtrStructure(m.modelSlicePtr)

err = m.operator.FindAll(m.ctx(), res, query, args...)

switch {
case err == nil:
return modelStructSlice2MapSlice(res, m.operator.GetDBTag()), nil
case errors.Is(err, ErrNotFound):
return []map[string]any{}, nil
default:
if err != nil {
return []map[string]any{}, err
}

result = modelStructSlice2MapSlice(res, m.operator.GetDBTag())

if m.hasCalled(ctlSelect) {
for i, row := range result {
result[i] = filterBySelectColumns(row, m.qs.GetSelectSQL())
}
}

return result, nil
}

// FindAllModel retrieves all records matching the current query set into a slice of models.
Expand All @@ -630,36 +656,26 @@ func (m *Impl) FindAllModel(modelSlicePtr any) (err error) {
}

rv := reflect.ValueOf(modelSlicePtr)
if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Slice {
return fmt.Errorf("model must be a pointer to slice")
if rv.Kind() != reflect.Pointer || rv.Elem().Kind() != reflect.Slice {
return fmt.Errorf(ModelTypeNotSliceError)
}

query, args := m.buildQuery(m.qs.GetSelectSQL())
query += m.qs.GetLimitSQL()

err = m.operator.FindAll(m.ctx(), modelSlicePtr, query, args...)

switch {
case err == nil:
return nil
case reflect.ValueOf(modelSlicePtr).Elem().Len() == 0:
return ErrNotFound
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
default:
return err
}
return err
}

// Delete marks the records as deleted by setting the 'is_deleted' field to true.
// It returns the number of records marked as deleted.
// Note: This method is not a true delete operation; it only marks records as deleted.
func (m *Impl) Delete() (num int64, err error) {
if err = m.preCheck("Delete", ctlGroupBy, ctlSelect, ctlOrderBy); err != nil {
if err = m.preCheck("Delete", ctlSelect, ctlGroupBy, ctlHaving); err != nil {
return 0, err
}

data := map[string]any{"is_deleted": true}

return m.Update(data)
return m.update(map[string]any{"is_deleted": true})
Comment thread
cursor[bot] marked this conversation as resolved.
}

func (m *Impl) exist() (exist bool, err error) {
Expand All @@ -680,7 +696,7 @@ func (m *Impl) Exist() (exist bool, err error) {
// List retrieves the total count and all data matching the current query set.
// It returns the total count, data as a slice of maps, and any error encountered.
func (m *Impl) List() (total int64, data []map[string]any, err error) {
if err = m.preCheck("List", ctlSelect, ctlHaving); err != nil {
if err = m.preCheck("List", ctlHaving); err != nil {
return 0, data, err
}

Expand Down Expand Up @@ -719,14 +735,16 @@ func (m *Impl) CreateOrUpdate(data map[string]any) (created bool, numOrID int64,
return false, 0, err
}

if exist, err := m.exist(); err != nil {
exist, err := m.exist()
if err != nil {
return false, 0, err
} else if exist {
if num, err := m.update(data); err != nil {
}
if exist {
num, err := m.update(data)
if err != nil {
return false, 0, err
} else {
return false, num, nil
}
return false, num, nil
}

m.reset()
Expand Down
Loading
Loading