funifier-mcp 0.3.20 → 0.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/funifier.mdc +1 -0
- package/.github/copilot-instructions.md +1 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +31 -0
- package/datasource-funifier-docs/.coverage.json +22 -6
- package/datasource-funifier-docs/.validation.json +87 -11
- package/datasource-funifier-docs/knowledge/guides/database-access.md +3 -0
- package/datasource-funifier-docs/knowledge/guides/explain-diagnostics.md +170 -0
- package/datasource-funifier-docs/knowledge/guides/server-utils.md +718 -0
- package/datasource-funifier-docs/knowledge/index.md +4 -0
- package/datasource-funifier-docs/knowledge/modules/custom-object.md +12 -7
- package/datasource-funifier-docs/knowledge/modules/database.md +48 -5
- package/dist/mcp/bundle.js +131 -108
- package/dist/mcp/tools/database.d.ts.map +1 -1
- package/dist/mcp/tools/database.js +14 -2
- package/dist/mcp/tools/database.js.map +1 -1
- package/dist/mcp/tools/database.test.js +16 -0
- package/dist/mcp/tools/database.test.js.map +1 -1
- package/dist/mcp/tools/db-explain.d.ts +4 -0
- package/dist/mcp/tools/db-explain.d.ts.map +1 -0
- package/dist/mcp/tools/db-explain.js +187 -0
- package/dist/mcp/tools/db-explain.js.map +1 -0
- package/dist/mcp/tools/db-explain.test.d.ts +2 -0
- package/dist/mcp/tools/db-explain.test.d.ts.map +1 -0
- package/dist/mcp/tools/db-explain.test.js +142 -0
- package/dist/mcp/tools/db-explain.test.js.map +1 -0
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +2 -0
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/list-tools.d.ts.map +1 -1
- package/dist/mcp/tools/list-tools.js +8 -0
- package/dist/mcp/tools/list-tools.js.map +1 -1
- package/package.json +1 -1
- package/skills/funifier/SKILL.md +1 -0
- package/skills/funifier/references/create-aggregate.md +10 -5
- package/skills/funifier/references/create-audit.md +0 -8
- package/skills/funifier/references/create-custom-object.md +0 -6
- package/skills/funifier/references/date-handling.md +0 -6
- package/skills/funifier/references/debug.md +2 -1
- package/skills/funifier/references/help.md +0 -6
- package/skills/funifier/references/manage-indexes.md +0 -6
- package/skills/funifier/references/query-aggregate.md +2 -6
- package/skills/funifier/references/server-utils.md +94 -0
- package/datasource-funifier-docs/.search-index.json +0 -59689
- package/datasource-funifier-docs/.skills-map.json +0 -150
|
@@ -62,6 +62,7 @@ Then read a result with `funifier_read_doc path=<path>`.
|
|
|
62
62
|
| `skills/funifier/references/upload-file.md` | Upload files to Funifier — images and documents for player avatars, challenge resources, and visual assets; use when adding media to the platform, not for CSV data import (use funifier-import-csv) or static frontend hosting (use funifier-implement-frontend) |
|
|
63
63
|
| `skills/funifier/references/manage-indexes.md` | Create, list, and drop MongoDB single-field indexes on Funifier collections via the REST API. Composite (multi-field) indexes are NOT supported by this endpoint — see Limitation section. |
|
|
64
64
|
| `skills/funifier/references/audit-permissions.md` | Cross-check a project's Funifier API calls against the live security document — scan code for usage, build an evidence-backed manifest, run the deterministic audit engine, and report missing scopes, excess tokens, and static danger findings; use when debugging 401s, auditing minimum privilege, or verifying permissions before deploying; audit is read-only and never changes any configuration |
|
|
65
|
+
| `skills/funifier/references/server-utils.md` | Use the server-side utility classes from com.funifier.engine.util in Groovy triggers, schedulers, and public endpoints — validates CPF, encrypts fields with AES/BCrypt/PGP, renders Mustache templates, evaluates inline expressions, computes object diffs/changelogs, converts JSON, generates Excel, makes HTTP calls, and handles files and pagination |
|
|
65
66
|
|
|
66
67
|
Full workflow files: `skills/funifier/references/`
|
|
67
68
|
|
|
@@ -55,6 +55,7 @@ Detailed guided workflows live in `skills/funifier/references/`:
|
|
|
55
55
|
| `skills/funifier/references/upload-file.md` | Upload files to Funifier — images and documents for player avatars, challenge resources, and visual assets; use when adding media to the platform, not for CSV data import (use funifier-import-csv) or static frontend hosting (use funifier-implement-frontend) |
|
|
56
56
|
| `skills/funifier/references/manage-indexes.md` | Create, list, and drop MongoDB single-field indexes on Funifier collections via the REST API. Composite (multi-field) indexes are NOT supported by this endpoint — see Limitation section. |
|
|
57
57
|
| `skills/funifier/references/audit-permissions.md` | Cross-check a project's Funifier API calls against the live security document — scan code for usage, build an evidence-backed manifest, run the deterministic audit engine, and report missing scopes, excess tokens, and static danger findings; use when debugging 401s, auditing minimum privilege, or verifying permissions before deploying; audit is read-only and never changes any configuration |
|
|
58
|
+
| `skills/funifier/references/server-utils.md` | Use the server-side utility classes from com.funifier.engine.util in Groovy triggers, schedulers, and public endpoints — validates CPF, encrypts fields with AES/BCrypt/PGP, renders Mustache templates, evaluates inline expressions, computes object diffs/changelogs, converts JSON, generates Excel, makes HTTP calls, and handles files and pagination |
|
|
58
59
|
|
|
59
60
|
### Funifier Modules
|
|
60
61
|
|
package/AGENTS.md
CHANGED
|
@@ -94,6 +94,7 @@ Before any `funifier_*` MCP tool call or Funifier task, find the matching row an
|
|
|
94
94
|
| `skills/funifier/references/upload-file.md` | Upload files to Funifier — images and documents for player avatars, challenge resources, and visual assets; use when adding media to the platform, not for CSV data import (use funifier-import-csv) or static frontend hosting (use funifier-implement-frontend) |
|
|
95
95
|
| `skills/funifier/references/manage-indexes.md` | Create, list, and drop MongoDB single-field indexes on Funifier collections via the REST API. Composite (multi-field) indexes are NOT supported by this endpoint — see Limitation section. |
|
|
96
96
|
| `skills/funifier/references/audit-permissions.md` | Cross-check a project's Funifier API calls against the live security document — scan code for usage, build an evidence-backed manifest, run the deterministic audit engine, and report missing scopes, excess tokens, and static danger findings; use when debugging 401s, auditing minimum privilege, or verifying permissions before deploying; audit is read-only and never changes any configuration |
|
|
97
|
+
| `skills/funifier/references/server-utils.md` | Use the server-side utility classes from com.funifier.engine.util in Groovy triggers, schedulers, and public endpoints — validates CPF, encrypts fields with AES/BCrypt/PGP, renders Mustache templates, evaluates inline expressions, computes object diffs/changelogs, converts JSON, generates Excel, makes HTTP calls, and handles files and pagination |
|
|
97
98
|
|
|
98
99
|
## Funifier Modules
|
|
99
100
|
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,37 @@
|
|
|
3
3
|
All notable changes to `funifier-mcp` are documented here. Format loosely follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); versions follow semver.
|
|
5
5
|
|
|
6
|
+
## [0.3.22]
|
|
7
|
+
|
|
8
|
+
New MCP tool `funifier_db_explain` and a full-replace warning on `funifier_database` updates.
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
- **New tool `funifier_db_explain`.** Returns the REAL MongoDB query plan (`winningPlan` /
|
|
12
|
+
`executionStats` — IXSCAN vs COLLSCAN, docs examined vs returned) for an `aggregate` or `find`
|
|
13
|
+
against a Funifier collection. Since the REST API cannot explain queries, it runs `explain` inside
|
|
14
|
+
the engine via a TEMPORARY scheduler (create → execute → delete) — nothing is left behind.
|
|
15
|
+
Inputs validated before touching the API (collection `^[A-Za-z0-9_]+$` injection guard, JSON
|
|
16
|
+
pipeline/filter); pipeline/filter travel as JSON strings in `extra` so no `$`-key is persisted;
|
|
17
|
+
cleanup runs in a `finally` block even on error.
|
|
18
|
+
- **`funifier_database` update: full-replace warning.** The tool now warns that a `PUT` update
|
|
19
|
+
replaces the whole document (no field merge).
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
- **Add `guides/explain-diagnostics.md`.** How to obtain the real MongoDB query plan on Funifier via
|
|
23
|
+
the scheduler escape-hatch, including the daily-execution limit and why the plan is real (not an
|
|
24
|
+
estimate). Registered in `knowledge/index.md` and cross-linked from `modules/database.md`.
|
|
25
|
+
|
|
26
|
+
## [0.3.21]
|
|
27
|
+
|
|
28
|
+
Documentation of the `com.funifier.engine.util` package as a new knowledge guide and Claude Code skill.
|
|
29
|
+
|
|
30
|
+
### Documentation
|
|
31
|
+
- **Add `guides/server-utils.md`.** Covers all 17 developer-facing classes in the Funifier server-side utilities package: `JsonUtil`, `StringUtil`, `CpfUtil`, `FormatterUtil`, `ParserUtil`, `ExpressionUtils`, `MustacheUtils`, `DiffUtil`, `AesCrypt`, `BCrypt`, `PgpCrypt`, `OtpAuthUtil`, `HttpUtil`, `UrlUtil`, `ExcelUtil`, `PaginationUtil`, and `FileUtil`. Each class has its FQN, real public signatures (verified against source), and ≥1 Groovy/Java server-side example. Security-sensitive classes (AesCrypt, BCrypt, PgpCrypt, OtpAuthUtil) include secret-management notes. `DateUtil` is cross-linked to `funifier-date-handling` instead of duplicated.
|
|
32
|
+
- **Register in `knowledge/index.md`.** New entry in the Java guides section so the knowledge router surfaces the guide.
|
|
33
|
+
|
|
34
|
+
### Skills
|
|
35
|
+
- **New skill `funifier-server-utils`** in `scripts/skills-config.ts` + regenerated reference. Triggers on requests like "validar CPF", "criptografar campo", "gerar TOTP", "renderizar template Mustache", "diff de objeto", "converter JSON", "gerar Excel" and similar.
|
|
36
|
+
|
|
6
37
|
## [0.3.20]
|
|
7
38
|
|
|
8
39
|
Documentation of trigger flow-interruption (strict mode) in the Funifier knowledge base.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-
|
|
2
|
+
"generatedAt": "2026-07-01T15:34:00.000Z",
|
|
3
3
|
"docsRoot": "datasource-funifier-docs/",
|
|
4
4
|
"docs": {
|
|
5
5
|
"knowledge/guides/aggregates.md": {
|
|
@@ -22,6 +22,14 @@
|
|
|
22
22
|
"funifier-implement-frontend"
|
|
23
23
|
]
|
|
24
24
|
},
|
|
25
|
+
"knowledge/guides/explain-diagnostics.md": {
|
|
26
|
+
"status": "grouped",
|
|
27
|
+
"skillIds": [
|
|
28
|
+
"funifier-query-aggregate",
|
|
29
|
+
"funifier-create-aggregate",
|
|
30
|
+
"funifier-debug"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
25
33
|
"knowledge/guides/java-entities.md": {
|
|
26
34
|
"status": "covered",
|
|
27
35
|
"skillIds": [
|
|
@@ -39,7 +47,8 @@
|
|
|
39
47
|
"status": "grouped",
|
|
40
48
|
"skillIds": [
|
|
41
49
|
"funifier-create-trigger",
|
|
42
|
-
"funifier-create-scheduler"
|
|
50
|
+
"funifier-create-scheduler",
|
|
51
|
+
"funifier-server-utils"
|
|
43
52
|
]
|
|
44
53
|
},
|
|
45
54
|
"knowledge/guides/permission-audit.md": {
|
|
@@ -48,6 +57,12 @@
|
|
|
48
57
|
"funifier-audit-permissions"
|
|
49
58
|
]
|
|
50
59
|
},
|
|
60
|
+
"knowledge/guides/server-utils.md": {
|
|
61
|
+
"status": "covered",
|
|
62
|
+
"skillIds": [
|
|
63
|
+
"funifier-server-utils"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
51
66
|
"knowledge/guides/trigger-examples.md": {
|
|
52
67
|
"status": "grouped",
|
|
53
68
|
"skillIds": [
|
|
@@ -59,7 +74,8 @@
|
|
|
59
74
|
"status": "grouped",
|
|
60
75
|
"skillIds": [
|
|
61
76
|
"funifier-create-trigger",
|
|
62
|
-
"funifier-debug"
|
|
77
|
+
"funifier-debug",
|
|
78
|
+
"funifier-server-utils"
|
|
63
79
|
]
|
|
64
80
|
},
|
|
65
81
|
"knowledge/index.md": {
|
|
@@ -338,9 +354,9 @@
|
|
|
338
354
|
}
|
|
339
355
|
},
|
|
340
356
|
"summary": {
|
|
341
|
-
"total":
|
|
342
|
-
"covered":
|
|
343
|
-
"grouped":
|
|
357
|
+
"total": 54,
|
|
358
|
+
"covered": 33,
|
|
359
|
+
"grouped": 15,
|
|
344
360
|
"deferred": 6,
|
|
345
361
|
"uncovered": 0
|
|
346
362
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-
|
|
2
|
+
"generatedAt": "2026-07-01T15:08:00Z",
|
|
3
3
|
"skills": {
|
|
4
4
|
"funifier-audit-permissions": {
|
|
5
5
|
"configHash": "f0569bdb0e11e1a2c6a11f6a4ff9cfd940668c7431bee06342b29faab32510fc",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
]
|
|
70
70
|
},
|
|
71
71
|
"funifier-create-aggregate": {
|
|
72
|
-
"configHash": "
|
|
72
|
+
"configHash": "8ddc1acdf7a961571399d0885722c0e702c00bca4990157ba500f81889b7ce16",
|
|
73
73
|
"status": "verified",
|
|
74
74
|
"claims": [
|
|
75
75
|
{
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
]
|
|
86
86
|
},
|
|
87
87
|
"funifier-create-audit": {
|
|
88
|
-
"configHash": "
|
|
88
|
+
"configHash": "c98ef7a985ad1d7a1c3c325b17aec630d3d99aa67372b640c271d7a1d2faf580",
|
|
89
89
|
"status": "verified",
|
|
90
90
|
"claims": [
|
|
91
91
|
{
|
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
]
|
|
190
190
|
},
|
|
191
191
|
"funifier-create-custom-object": {
|
|
192
|
-
"configHash": "
|
|
192
|
+
"configHash": "6fb0edb78fecd25705a6e8b7d4c683c65c9673994bf24cf77d062e533d35eb0a",
|
|
193
193
|
"status": "verified",
|
|
194
194
|
"claims": [
|
|
195
195
|
{
|
|
@@ -452,7 +452,7 @@
|
|
|
452
452
|
]
|
|
453
453
|
},
|
|
454
454
|
"funifier-create-trigger": {
|
|
455
|
-
"configHash": "
|
|
455
|
+
"configHash": "511b1e5e8d9211eb5149e40eb2bd700014b04ead15401fb9d2812511d5fe3842",
|
|
456
456
|
"status": "verified",
|
|
457
457
|
"claims": [
|
|
458
458
|
{
|
|
@@ -484,6 +484,16 @@
|
|
|
484
484
|
"symbol": "Class:src/main/java/com/funifier/engine/integration/trigger/TriggerManager.java:TriggerManager",
|
|
485
485
|
"file": "src/main/java/com/funifier/engine/integration/trigger/TriggerManager.java"
|
|
486
486
|
}
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"claim": "TriggerManager.executeStrict rethrows the first collected exception as a FunifierException (HTTP 500), and DatabaseRest.insert/update/delete call it for before_create/before_update/before_delete — so a throw in those before_ events on a database collection aborts the operation. This is version-dependent (not on every instance). Plain execute() swallows exceptions.",
|
|
490
|
+
"gitnexusQuery": "executeStrict",
|
|
491
|
+
"critical": true,
|
|
492
|
+
"status": "verified",
|
|
493
|
+
"evidence": {
|
|
494
|
+
"symbol": "Method:src/main/java/com/funifier/engine/integration/trigger/TriggerManager.java:TriggerManager.executeStrict#6",
|
|
495
|
+
"file": "src/main/java/com/funifier/engine/integration/trigger/TriggerManager.java"
|
|
496
|
+
}
|
|
487
497
|
}
|
|
488
498
|
]
|
|
489
499
|
},
|
|
@@ -552,12 +562,12 @@
|
|
|
552
562
|
]
|
|
553
563
|
},
|
|
554
564
|
"funifier-date-handling": {
|
|
555
|
-
"configHash": "
|
|
565
|
+
"configHash": "dfb22a3c66b117006b5fff0e11e8f9293051463a39235fb33cf174ea321f6e20",
|
|
556
566
|
"status": "verified",
|
|
557
567
|
"claims": []
|
|
558
568
|
},
|
|
559
569
|
"funifier-debug": {
|
|
560
|
-
"configHash": "
|
|
570
|
+
"configHash": "c881d7adc2585c1aebbb053677ec219ec4d869441aaf42d4e8dff72eccde6f2f",
|
|
561
571
|
"status": "verified",
|
|
562
572
|
"claims": [
|
|
563
573
|
{
|
|
@@ -573,7 +583,7 @@
|
|
|
573
583
|
]
|
|
574
584
|
},
|
|
575
585
|
"funifier-help": {
|
|
576
|
-
"configHash": "
|
|
586
|
+
"configHash": "e2abfdc80de3bfd4200d93d727675ccf51f76afa60293fd0f38158ffc5b9e9ef",
|
|
577
587
|
"status": "verified",
|
|
578
588
|
"claims": [
|
|
579
589
|
{
|
|
@@ -589,7 +599,7 @@
|
|
|
589
599
|
]
|
|
590
600
|
},
|
|
591
601
|
"funifier-implement-frontend": {
|
|
592
|
-
"configHash": "
|
|
602
|
+
"configHash": "086904fe521764c1e7846ce01babd1b4c77e14f33f31447890d5e4564c856905",
|
|
593
603
|
"status": "verified",
|
|
594
604
|
"claims": [
|
|
595
605
|
{
|
|
@@ -620,7 +630,7 @@
|
|
|
620
630
|
"claims": []
|
|
621
631
|
},
|
|
622
632
|
"funifier-manage-indexes": {
|
|
623
|
-
"configHash": "
|
|
633
|
+
"configHash": "7ff9fa3b1b6a5725b341f58711c4e8bf8521e39c5a16a4254478448202e56d55",
|
|
624
634
|
"status": "verified",
|
|
625
635
|
"claims": []
|
|
626
636
|
},
|
|
@@ -667,10 +677,76 @@
|
|
|
667
677
|
]
|
|
668
678
|
},
|
|
669
679
|
"funifier-query-aggregate": {
|
|
670
|
-
"configHash": "
|
|
680
|
+
"configHash": "272b0e297357bfdd9a9a79c945eef5ded46aa3029242d1907eb56658d86d3ea6",
|
|
671
681
|
"status": "verified",
|
|
672
682
|
"claims": []
|
|
673
683
|
},
|
|
684
|
+
"funifier-server-utils": {
|
|
685
|
+
"configHash": "34d9bfa05517ff3b5d23000d0bb421006c1eb71c176be600df7d6c3cc48bc1d7",
|
|
686
|
+
"status": "verified",
|
|
687
|
+
"claims": [
|
|
688
|
+
{
|
|
689
|
+
"claim": "CpfUtil.isValid(Object cpf) accepts String (formatted or plain) or Number (Long) and returns false for null; isValidCNPJ is commented out and unavailable",
|
|
690
|
+
"gitnexusQuery": "CpfUtil",
|
|
691
|
+
"critical": true,
|
|
692
|
+
"status": "verified",
|
|
693
|
+
"evidence": {
|
|
694
|
+
"symbol": "Class:src/main/java/com/funifier/engine/util/CpfUtil.java:CpfUtil",
|
|
695
|
+
"file": "src/main/java/com/funifier/engine/util/CpfUtil.java"
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
"claim": "AesCrypt.encryptFields(Object object, List<String> fields, String secret) accepts dot-notation JSONPath field names and returns the same object type with those fields AES-encrypted as Base64",
|
|
700
|
+
"gitnexusQuery": "AesCrypt",
|
|
701
|
+
"critical": true,
|
|
702
|
+
"status": "verified",
|
|
703
|
+
"evidence": {
|
|
704
|
+
"symbol": "Class:src/main/java/com/funifier/engine/util/AesCrypt.java:AesCrypt",
|
|
705
|
+
"file": "src/main/java/com/funifier/engine/util/AesCrypt.java"
|
|
706
|
+
}
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
"claim": "OtpAuthUtil.createQRCode is the only active public method; generateSecretKey, getTOTPCode, validateTOTPCode, and getGoogleAuthenticatorBarCode are commented out in the current source",
|
|
710
|
+
"gitnexusQuery": "OtpAuthUtil",
|
|
711
|
+
"critical": true,
|
|
712
|
+
"status": "verified",
|
|
713
|
+
"evidence": {
|
|
714
|
+
"symbol": "Class:src/main/java/com/funifier/engine/util/OtpAuthUtil.java:OtpAuthUtil",
|
|
715
|
+
"file": "src/main/java/com/funifier/engine/util/OtpAuthUtil.java"
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
"claim": "JsonUtil.fromJsonToMap(String json) returns Map<String, Object> parsed via DBObject.toMap(); JsonUtil.toJson produces indented JSON with nulls included",
|
|
720
|
+
"gitnexusQuery": "JsonUtil",
|
|
721
|
+
"critical": false,
|
|
722
|
+
"status": "verified",
|
|
723
|
+
"evidence": {
|
|
724
|
+
"symbol": "Class:src/main/java/com/funifier/engine/util/JsonUtil.java:JsonUtil",
|
|
725
|
+
"file": "src/main/java/com/funifier/engine/util/JsonUtil.java"
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
"claim": "DiffUtil.buildChangelogFromSequence(List<?> data, String sortBy, Config config) produces a List where each entry is the new version's fields plus a 'changes' key with diffAsMap output",
|
|
730
|
+
"gitnexusQuery": "DiffUtil",
|
|
731
|
+
"critical": false,
|
|
732
|
+
"status": "verified",
|
|
733
|
+
"evidence": {
|
|
734
|
+
"symbol": "Class:src/main/java/com/funifier/engine/util/DiffUtil.java:DiffUtil",
|
|
735
|
+
"file": "src/main/java/com/funifier/engine/util/DiffUtil.java"
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
"claim": "ExcelUtil.generateExcelFromObjects(List<Map<String, Object>> listRows, String sheetName) returns byte[] XLSX; column headers come from the key set of the first Map",
|
|
740
|
+
"gitnexusQuery": "ExcelUtil",
|
|
741
|
+
"critical": false,
|
|
742
|
+
"status": "verified",
|
|
743
|
+
"evidence": {
|
|
744
|
+
"symbol": "Class:src/main/java/com/funifier/engine/util/ExcelUtil.java:ExcelUtil",
|
|
745
|
+
"file": "src/main/java/com/funifier/engine/util/ExcelUtil.java"
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
]
|
|
749
|
+
},
|
|
674
750
|
"funifier-upload-file": {
|
|
675
751
|
"configHash": "9c3dcaf7351189c3395ae3ea5ac122d5b470b3e1db0b0fd94384880c0094f19c",
|
|
676
752
|
"status": "verified",
|
|
@@ -44,6 +44,8 @@ Este documento descreve as duas formas de acessar o MongoDB da Funifier — via
|
|
|
44
44
|
|
|
45
45
|
> ⚠️ **Use `strict=true` em todo GET/escrita de campos tipados.** Consequência de omitir: `Date` é lido como número e regravado com tipo errado, quebrando queries por data (`patterns.md` §9).
|
|
46
46
|
|
|
47
|
+
> ⚠️ **`DELETE` de muitos registros tem duas armadilhas.** `> 20.000` docs = **no-op silencioso** (200, nada apaga); milhares `≤ 20.000` = **timeout** (síncrono: 3 varreduras + materializa todos os ids). → Apague em **lotes de 1.000 sobre campo indexado** (`modules/database.md` §2.4/§10.5).
|
|
48
|
+
|
|
47
49
|
### 1.6 Documentos relacionados
|
|
48
50
|
|
|
49
51
|
> 📄 `datasource-funifier-docs/knowledge/guides/aggregates.md` — pipelines de aggregate e expressões de data
|
|
@@ -103,6 +105,7 @@ POST /v3/database/:collection/aggregate
|
|
|
103
105
|
- **`_filter`/`_sort`/`_limit`** → ignorados (§1.5). Correção: `q` + aggregate `$sort`.
|
|
104
106
|
- **`PUT` parcial** → replace apaga campos (§1.5). Correção: objeto completo.
|
|
105
107
|
- **`POST` esperando busca** → cria documento (§1.5). Correção: `GET ?q=`.
|
|
108
|
+
- **`DELETE` com filtro amplo** → duas armadilhas: `> 20.000` docs = **no-op silencioso** (200, nada apaga); milhares `≤ 20.000` = **timeout** (operação síncrona, 3 varreduras + materializa todos os ids num único request). Correção: apague em **lotes de 1.000 sobre campo indexado** (`modules/database.md` §2.4/§10.5).
|
|
106
109
|
|
|
107
110
|
---
|
|
108
111
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Diagnóstico de Queries — `explain()` do MongoDB via Scheduler
|
|
2
|
+
|
|
3
|
+
## 1. Visão Geral
|
|
4
|
+
|
|
5
|
+
### 1.1 O que é este documento
|
|
6
|
+
|
|
7
|
+
Descreve como obter o **plano de consulta real** do MongoDB (`explain`) na Funifier — `winningPlan`,
|
|
8
|
+
`IXSCAN` vs `COLLSCAN`, `totalDocsExamined` — mesmo sem endpoint nativo de explain. A técnica roda o
|
|
9
|
+
comando `explain` **dentro do engine**, num script server-side (scheduler), e devolve o plano de forma
|
|
10
|
+
síncrona. É a única forma de ver o plano real de uma query específica sem alterar o `funifier-service`.
|
|
11
|
+
|
|
12
|
+
### 1.2 Quando consultar
|
|
13
|
+
|
|
14
|
+
- Ao **diagnosticar uma query lenta** (aggregate ou find) e precisar saber se ela usa índice.
|
|
15
|
+
- Ao investigar **timeout de `DELETE`** ou varreduras caras — confirmar `COLLSCAN` vs `IXSCAN`.
|
|
16
|
+
- Ao **validar um índice recém-criado** — provar que a query passou a usá-lo.
|
|
17
|
+
- Antes de otimizar: medir `totalDocsExamined` vs `nReturned` (quanto maior a razão, pior).
|
|
18
|
+
|
|
19
|
+
### 1.3 Quando NÃO consultar
|
|
20
|
+
|
|
21
|
+
- **NÃO** consulte para montar pipelines (`$match`/`$group`/`$sort`) nem expressões de data — use
|
|
22
|
+
`guides/aggregates.md`.
|
|
23
|
+
- **NÃO** consulte para criar/listar índices — use `guides/database-access.md` §5 e o endpoint
|
|
24
|
+
`/v3/database/:collection/index`.
|
|
25
|
+
- **NÃO** use isto quando só precisa de **estatística de índices/coleção** (uso por índice, tamanho):
|
|
26
|
+
aí basta `$indexStats`/`$collStats` como primeiro estágio de um aggregate normal (§5), sem scheduler.
|
|
27
|
+
|
|
28
|
+
### 1.4 Índice de decisão
|
|
29
|
+
|
|
30
|
+
| Problema / Situação | O que fazer | Seção |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| Ver o plano real de um aggregate | `explain` do `aggregate` via scheduler | §2, §3 |
|
|
33
|
+
| Ver o plano real de um find/delete | `explain` do `find` com o mesmo `filter` | §3 |
|
|
34
|
+
| Saber se bate no índice | Ler `winningPlan.stage` (`IXSCAN`/`COLLSCAN`) | §4 |
|
|
35
|
+
| Medir custo real (docs examinados) | `verbosity: "executionStats"` | §3, §4 |
|
|
36
|
+
| Só uso/tamanho de índices | `$indexStats` / `$collStats` (sem scheduler) | §5 |
|
|
37
|
+
|
|
38
|
+
### 1.5 Restrições globais críticas
|
|
39
|
+
|
|
40
|
+
> ⚠️ **`explain: true` é uma flag de comando (`runCommand`), não um estágio de pipeline.** Consequência:
|
|
41
|
+
> **não** dá para obtê-lo pelo endpoint `/v3/database/{collection}/aggregate` (que só *executa* a query).
|
|
42
|
+
> → Rode `explain` num script server-side com `manager.getJongoConnection().runCommand(...)`.
|
|
43
|
+
|
|
44
|
+
> ⚠️ **Cada execução consome uma execução diária de scheduler** (há limite por gamificação). Consequência:
|
|
45
|
+
> diagnósticos em massa podem esgotar a cota. → Use pontualmente; não coloque em loop.
|
|
46
|
+
|
|
47
|
+
> ⚠️ **Nunca persista o pipeline/filtro como sub-documento com chaves `$`.** O MongoDB rejeita chaves
|
|
48
|
+
> iniciadas por `$` em documentos gravados. → Guarde o pipeline como **string JSON** em `scheduler.extra`
|
|
49
|
+
> e concatene o comando no script (§3), ou embuta o JSON direto no script.
|
|
50
|
+
|
|
51
|
+
> ⚠️ **O sandbox de scripts (`TriggerExpressionChecker`) bloqueia `.execute`, `.getDB`, `.getMongo`,
|
|
52
|
+
> `.dropDatabase`.** Consequência: `jongo.getDatabase().command(...)` casa com `.getDB`? Não — mas evite
|
|
53
|
+
> caminhos bloqueados. → Use `jongo.runCommand(...)` (permitido) — é o caminho canónico e testado.
|
|
54
|
+
|
|
55
|
+
### 1.6 Documentos relacionados
|
|
56
|
+
|
|
57
|
+
- `guides/database-access.md` — mecânica de acesso (Jongo, `manager.getJongoConnection()`, índices).
|
|
58
|
+
- `guides/aggregates.md` — montagem de pipelines e expressões de data.
|
|
59
|
+
- `guides/java-libraries.md` — `JsonUtil`, Jongo.
|
|
60
|
+
- `modules/scheduler.md` — recurso Scheduler e execução via `/v3/scheduler/execute/{id}`.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 2. Como funciona (mecânica)
|
|
65
|
+
|
|
66
|
+
1. **Criar** um scheduler temporário (`POST /v3/scheduler`) com `active:false` (não agenda job Quartz)
|
|
67
|
+
e um `cron` Quartz válido (o compilador valida a expressão).
|
|
68
|
+
2. **Executar** de forma síncrona: `GET /v3/scheduler/execute/{id}` → roda o script na hora e devolve
|
|
69
|
+
`{ scheduler, outputs, exceptions, millis }`. O que o script `println`-a cai em **`outputs`**.
|
|
70
|
+
3. **Apagar** o scheduler (`DELETE /v3/scheduler/{id}`) — sempre, mesmo em erro.
|
|
71
|
+
|
|
72
|
+
O script tem acesso ao driver Mongo nativo via `manager.getJongoConnection()`, então executa o
|
|
73
|
+
comando `explain` de verdade — o mesmo que `db.runCommand({ explain: ... })` no shell.
|
|
74
|
+
|
|
75
|
+
> O id enviado no campo `_id` é honrado pelo servidor (`SchedulerConfig.id` é `@JsonProperty("_id")`),
|
|
76
|
+
> então o mesmo id serve para `execute` e `delete`.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 3. O script (Groovy)
|
|
81
|
+
|
|
82
|
+
Sandbox-legal (só `runCommand`, acesso a propriedade e concatenação de string). Os dados chegam como
|
|
83
|
+
**strings JSON** em `scheduler.extra` para nunca persistir chaves `$`:
|
|
84
|
+
|
|
85
|
+
```groovy
|
|
86
|
+
void execute(scheduler){
|
|
87
|
+
def x = scheduler.extra
|
|
88
|
+
def jongo = manager.getJongoConnection()
|
|
89
|
+
def cmd
|
|
90
|
+
if (x.operation == 'find') {
|
|
91
|
+
cmd = '{ explain: { find: "' + x.collection + '", filter: ' + x.filterJson + ' }, verbosity: "' + x.verbosity + '" }'
|
|
92
|
+
} else {
|
|
93
|
+
cmd = '{ explain: { aggregate: "' + x.collection + '", pipeline: ' + x.pipelineJson + ', cursor: {} }, verbosity: "' + x.verbosity + '" }'
|
|
94
|
+
}
|
|
95
|
+
def plan = jongo.runCommand(cmd).as(java.util.Map.class)
|
|
96
|
+
println JsonUtil.toJson(plan)
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`extra` de exemplo (aggregate):
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"operation": "aggregate",
|
|
105
|
+
"collection": "action_log",
|
|
106
|
+
"verbosity": "executionStats",
|
|
107
|
+
"pipelineJson": "[{\"$match\":{\"time\":{\"$gt\":0}}}]"
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Formas do comando `explain` (MongoDB 5.0):
|
|
112
|
+
|
|
113
|
+
- **aggregate:** `{ explain: { aggregate: "<col>", pipeline: [...], cursor: {} }, verbosity: "<v>" }`
|
|
114
|
+
- **find:** `{ explain: { find: "<col>", filter: {...} }, verbosity: "<v>" }`
|
|
115
|
+
|
|
116
|
+
`verbosity`: `queryPlanner` (só escolhe o plano, não executa) · `executionStats` (executa e mede docs
|
|
117
|
+
examinados — melhor para diagnóstico) · `allPlansExecution` (inclui planos rejeitados).
|
|
118
|
+
|
|
119
|
+
> Valide `collection` com `^[A-Za-z0-9_]+$` antes de concatenar — evita quebrar o JSON do comando.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 4. Interpretando o resultado
|
|
124
|
+
|
|
125
|
+
Resposta real (find sem filtro em `player`, `executionStats`, MongoDB 5.0.29):
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"queryPlanner": { "winningPlan": { "stage": "COLLSCAN", "direction": "forward" }, "rejectedPlans": [] },
|
|
130
|
+
"executionStats": {
|
|
131
|
+
"nReturned": 55,
|
|
132
|
+
"executionTimeMillis": 1,
|
|
133
|
+
"totalKeysExamined": 0,
|
|
134
|
+
"totalDocsExamined": 55,
|
|
135
|
+
"executionStages": { "stage": "COLLSCAN", "docsExamined": 55 }
|
|
136
|
+
},
|
|
137
|
+
"ok": 1
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Sinais a observar:
|
|
142
|
+
|
|
143
|
+
| Campo | O que significa |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `winningPlan.stage = COLLSCAN` | ⚠️ Varredura completa — **nenhum índice** foi usado. Candidato a criar índice. |
|
|
146
|
+
| `winningPlan.stage = IXSCAN` (com `indexName`) | ✅ Usou índice. |
|
|
147
|
+
| `totalDocsExamined` >> `nReturned` | ⚠️ Examinou muito mais do que devolveu — filtro sem índice ou índice ruim. |
|
|
148
|
+
| `totalKeysExamined = 0` | Não tocou em nenhum índice (confirma `COLLSCAN`). |
|
|
149
|
+
| `rejectedPlans` não vazio | Havia índices alternativos; útil ver por que perderam. |
|
|
150
|
+
|
|
151
|
+
Para o problema clássico de **timeout de `DELETE` sobre campo indexado**: explique um `find` com o
|
|
152
|
+
**mesmo `filter` do delete**. Se der `COLLSCAN`, o delete varre a coleção inteira → crie o índice e
|
|
153
|
+
apague em lotes (`guides/database-access.md`, `modules/database.md`).
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 5. Alternativa sem scheduler (só estatística de índices)
|
|
158
|
+
|
|
159
|
+
Se você **não** precisa do plano de uma query específica, mas só de diagnóstico de índices/coleção,
|
|
160
|
+
use estágios de agregação que passam pelo endpoint normal (`POST /v3/database/{collection}/aggregate`),
|
|
161
|
+
como **primeiro** estágio — sem scheduler:
|
|
162
|
+
|
|
163
|
+
| Objetivo | Primeiro estágio |
|
|
164
|
+
|---|---|
|
|
165
|
+
| Uso/acesso por índice (índice morto?) | `[{ "$indexStats": {} }]` |
|
|
166
|
+
| Tamanho da coleção, contagem, tamanho dos índices | `[{ "$collStats": { "storageStats": {} } }]` |
|
|
167
|
+
| Planos em cache | `[{ "$planCacheStats": {} }]` |
|
|
168
|
+
|
|
169
|
+
Limitação: isto mostra **quais índices existem e o uso deles**, mas **não** o `winningPlan` de uma
|
|
170
|
+
query concreta — para isso, use o `explain` via scheduler (§2–§4).
|