funifier-mcp 0.3.21 → 0.4.0
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/CHANGELOG.md +46 -0
- package/README.md +15 -11
- package/datasource-funifier-docs/.coverage.json +11 -3
- 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/index.md +3 -0
- package/datasource-funifier-docs/knowledge/modules/custom-object.md +12 -7
- package/datasource-funifier-docs/knowledge/modules/database.md +48 -5
- package/dist/cli/config-writers.d.ts +1 -3
- package/dist/cli/config-writers.d.ts.map +1 -1
- package/dist/cli/config-writers.js +20 -11
- package/dist/cli/config-writers.js.map +1 -1
- package/dist/cli/config-writers.test.js +25 -18
- package/dist/cli/config-writers.test.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +37 -36
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/init.test.js +51 -17
- package/dist/cli/init.test.js.map +1 -1
- package/dist/cli/prompts.d.ts +1 -0
- package/dist/cli/prompts.d.ts.map +1 -1
- package/dist/cli/prompts.js +9 -0
- package/dist/cli/prompts.js.map +1 -1
- package/dist/mcp/bundle.js +132 -109
- 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/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/manage-indexes.md +0 -6
- package/skills/funifier/references/query-aggregate.md +2 -6
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,52 @@
|
|
|
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.4.0]
|
|
7
|
+
|
|
8
|
+
Setup now runs the MCP via `npx` instead of installing it into your project — eliminates the
|
|
9
|
+
dependency conflicts that `npm install funifier-mcp --save` caused in Node.js apps.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **`funifier-mcp init` no longer installs the package into the consumer project.** The old flow
|
|
13
|
+
ran `npm install funifier-mcp@latest --save` (and `npm init -y`), mutating the project's
|
|
14
|
+
`package.json`, lockfile, and `node_modules` — so the MCP's own deps (`axios`, `zod`,
|
|
15
|
+
`@modelcontextprotocol/sdk`, `@inquirer/prompts`) collided with the host app's pinned versions.
|
|
16
|
+
Setup is now zero-install.
|
|
17
|
+
- **Generated MCP configs run the server via `npx`.** All platforms (Claude Code, Cursor,
|
|
18
|
+
Antigravity, Codex, OpenCode, Kimi) now emit `command: "npx"`, `args: ["-y", "funifier-mcp@latest"]`
|
|
19
|
+
instead of `command: "node"` + an absolute `node_modules/.../bundle.js` path. npx caches the
|
|
20
|
+
package in its own store (`~/.npm/_npx`) and never touches the project. Matches the
|
|
21
|
+
industry-standard stdio MCP distribution pattern (official MCP docs, `@modelcontextprotocol/server-*`).
|
|
22
|
+
- **Dropped the hardcoded `FUNIFIER_DOCS_PATH` from generated configs.** The server auto-resolves
|
|
23
|
+
its bundled docs from the running package, so the env var is no longer written.
|
|
24
|
+
|
|
25
|
+
### Migration
|
|
26
|
+
- Re-running `funifier-mcp init` over an existing setup rewrites the configs to the npx form and,
|
|
27
|
+
when it detects a project-local `funifier-mcp` (in `package.json` or `node_modules`), offers to
|
|
28
|
+
run `npm uninstall funifier-mcp` to clean up the stale dependency.
|
|
29
|
+
- **Requirement:** Node.js with `npx` on `PATH` (ships with npm). Windows clients that can't find
|
|
30
|
+
`npx` can use `npx.cmd`; air-gapped setups can point `command`/`args` at a local install (see README).
|
|
31
|
+
|
|
32
|
+
## [0.3.22]
|
|
33
|
+
|
|
34
|
+
New MCP tool `funifier_db_explain` and a full-replace warning on `funifier_database` updates.
|
|
35
|
+
|
|
36
|
+
### Features
|
|
37
|
+
- **New tool `funifier_db_explain`.** Returns the REAL MongoDB query plan (`winningPlan` /
|
|
38
|
+
`executionStats` — IXSCAN vs COLLSCAN, docs examined vs returned) for an `aggregate` or `find`
|
|
39
|
+
against a Funifier collection. Since the REST API cannot explain queries, it runs `explain` inside
|
|
40
|
+
the engine via a TEMPORARY scheduler (create → execute → delete) — nothing is left behind.
|
|
41
|
+
Inputs validated before touching the API (collection `^[A-Za-z0-9_]+$` injection guard, JSON
|
|
42
|
+
pipeline/filter); pipeline/filter travel as JSON strings in `extra` so no `$`-key is persisted;
|
|
43
|
+
cleanup runs in a `finally` block even on error.
|
|
44
|
+
- **`funifier_database` update: full-replace warning.** The tool now warns that a `PUT` update
|
|
45
|
+
replaces the whole document (no field merge).
|
|
46
|
+
|
|
47
|
+
### Documentation
|
|
48
|
+
- **Add `guides/explain-diagnostics.md`.** How to obtain the real MongoDB query plan on Funifier via
|
|
49
|
+
the scheduler escape-hatch, including the daily-execution limit and why the plan is real (not an
|
|
50
|
+
estimate). Registered in `knowledge/index.md` and cross-linked from `modules/database.md`.
|
|
51
|
+
|
|
6
52
|
## [0.3.21]
|
|
7
53
|
|
|
8
54
|
Documentation of the `com.funifier.engine.util` package as a new knowledge guide and Claude Code skill.
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ Once the MCP server is connected, agents discover and read docs through MCP tool
|
|
|
42
42
|
|
|
43
43
|
## MCP Server Setup
|
|
44
44
|
|
|
45
|
-
The MCP server exposes Funifier as tools in any MCP-compatible AI client. `npx funifier-mcp@latest init` configures it automatically.
|
|
45
|
+
The MCP server exposes Funifier as tools in any MCP-compatible AI client. `npx funifier-mcp@latest init` configures it automatically. The server runs via **`npx`** — it is never installed into your project, so there are no dependency conflicts with your app's `package.json`/lockfile. The only requirement is **Node.js with `npx` on your `PATH`** (npx ships with npm). For manual setup, add the block below to your client config:
|
|
46
46
|
|
|
47
47
|
### Claude Code / Cursor / Antigravity (`.mcp.json`)
|
|
48
48
|
|
|
@@ -50,8 +50,8 @@ The MCP server exposes Funifier as tools in any MCP-compatible AI client. `npx f
|
|
|
50
50
|
{
|
|
51
51
|
"mcpServers": {
|
|
52
52
|
"funifier": {
|
|
53
|
-
"command": "
|
|
54
|
-
"args": ["
|
|
53
|
+
"command": "npx",
|
|
54
|
+
"args": ["-y", "funifier-mcp@latest"],
|
|
55
55
|
"env": {
|
|
56
56
|
"FUNIFIER_API_KEY": "your-api-key",
|
|
57
57
|
"FUNIFIER_SECRET_KEY": "your-secret-key",
|
|
@@ -66,8 +66,8 @@ The MCP server exposes Funifier as tools in any MCP-compatible AI client. `npx f
|
|
|
66
66
|
|
|
67
67
|
```toml
|
|
68
68
|
[mcp_servers.funifier]
|
|
69
|
-
command = "
|
|
70
|
-
args = ["
|
|
69
|
+
command = "npx"
|
|
70
|
+
args = ["-y", "funifier-mcp@latest"]
|
|
71
71
|
enabled = true
|
|
72
72
|
|
|
73
73
|
[mcp_servers.funifier.env]
|
|
@@ -83,7 +83,7 @@ FUNIFIER_SERVER_URL = "https://your-instance.funifier.com"
|
|
|
83
83
|
"mcp": {
|
|
84
84
|
"funifier": {
|
|
85
85
|
"type": "local",
|
|
86
|
-
"command": ["
|
|
86
|
+
"command": ["npx", "-y", "funifier-mcp@latest"],
|
|
87
87
|
"environment": {
|
|
88
88
|
"FUNIFIER_API_KEY": "your-api-key",
|
|
89
89
|
"FUNIFIER_SECRET_KEY": "your-secret-key",
|
|
@@ -94,6 +94,10 @@ FUNIFIER_SERVER_URL = "https://your-instance.funifier.com"
|
|
|
94
94
|
}
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
> **Windows:** if your client can't find `npx`, set `"command": "npx.cmd"` (or wrap it as `"command": "cmd", "args": ["/c", "npx", "-y", "funifier-mcp@latest"]`).
|
|
98
|
+
>
|
|
99
|
+
> **Air-gapped / offline:** npx needs network access on first run to fetch the package. Where that isn't possible, install `funifier-mcp` anywhere you like and point the client at its bin directly — e.g. `"command": "node", "args": ["/path/to/funifier-mcp/dist/mcp/index.js"]`.
|
|
100
|
+
|
|
97
101
|
### Runtime connection
|
|
98
102
|
|
|
99
103
|
If no config is provided at startup, use the `funifier_connect` tool to connect:
|
|
@@ -149,12 +153,12 @@ Restore refuses cross-server application when the snapshot's `serverUrl` differs
|
|
|
149
153
|
|
|
150
154
|
## Skills and Instructions
|
|
151
155
|
|
|
152
|
-
`npx funifier-mcp@latest init` copies the right files for each platform automatically. For manual
|
|
156
|
+
`npx funifier-mcp@latest init` copies the right files for each platform automatically (recommended). For manual copying, use `npm install --no-save` so nothing is added to your project's `package.json`/lockfile:
|
|
153
157
|
|
|
154
158
|
### Claude Code
|
|
155
159
|
|
|
156
160
|
```bash
|
|
157
|
-
npm install funifier-mcp
|
|
161
|
+
npm install --no-save funifier-mcp
|
|
158
162
|
cp -r node_modules/funifier-mcp/skills/* .claude/skills/
|
|
159
163
|
```
|
|
160
164
|
|
|
@@ -167,7 +171,7 @@ Start with `/funifier` in Claude Code — the router skill routes to the right w
|
|
|
167
171
|
### Codex CLI / OpenCode
|
|
168
172
|
|
|
169
173
|
```bash
|
|
170
|
-
npm install funifier-mcp
|
|
174
|
+
npm install --no-save funifier-mcp
|
|
171
175
|
cp node_modules/funifier-mcp/AGENTS.md .
|
|
172
176
|
```
|
|
173
177
|
|
|
@@ -176,7 +180,7 @@ The `AGENTS.md` is the entry point for Codex, OpenCode, and any agent that reads
|
|
|
176
180
|
### GitHub Copilot / Continue
|
|
177
181
|
|
|
178
182
|
```bash
|
|
179
|
-
npm install funifier-mcp
|
|
183
|
+
npm install --no-save funifier-mcp
|
|
180
184
|
mkdir -p .github
|
|
181
185
|
cp node_modules/funifier-mcp/.github/copilot-instructions.md .github/
|
|
182
186
|
```
|
|
@@ -186,7 +190,7 @@ Copilot and Continue automatically apply `.github/copilot-instructions.md`.
|
|
|
186
190
|
### Cursor IDE
|
|
187
191
|
|
|
188
192
|
```bash
|
|
189
|
-
npm install funifier-mcp
|
|
193
|
+
npm install --no-save funifier-mcp
|
|
190
194
|
mkdir -p .cursor/rules
|
|
191
195
|
cp node_modules/funifier-mcp/.cursor/rules/funifier.mdc .cursor/rules/
|
|
192
196
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-
|
|
2
|
+
"generatedAt": "2026-07-01T20:31: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": [
|
|
@@ -346,9 +354,9 @@
|
|
|
346
354
|
}
|
|
347
355
|
},
|
|
348
356
|
"summary": {
|
|
349
|
-
"total":
|
|
357
|
+
"total": 54,
|
|
350
358
|
"covered": 33,
|
|
351
|
-
"grouped":
|
|
359
|
+
"grouped": 15,
|
|
352
360
|
"deferred": 6,
|
|
353
361
|
"uncovered": 0
|
|
354
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).
|
|
@@ -103,6 +103,7 @@ A Funifier funciona como um **backend de aplicações**. Independentemente do ti
|
|
|
103
103
|
| Triggers | `guides/triggers-guide.md` | Guia completo de triggers | Referência de eventos, entidades, managers e exemplos de código Java |
|
|
104
104
|
| Trigger Examples | `guides/trigger-examples.md` | Exemplos de produção de triggers | Código Groovy pronto para padrões reais: atualizar coleção relacionada, criar achievement, inicializar jogador, normalizar/rejeitar `__c`, processar lote, registrar ação via `track` |
|
|
105
105
|
| Database Access | `guides/database-access.md` | Acesso ao banco de dados | Referência de acesso via API REST e via código Java (Jongo) |
|
|
106
|
+
| Explain / Diagnóstico | `guides/explain-diagnostics.md` | Plano de consulta real (`explain`) do MongoDB | Diagnosticar query lenta / timeout de `DELETE`: obter `winningPlan`, `IXSCAN` vs `COLLSCAN`, `totalDocsExamined` via scheduler (`runCommand explain`), já que o endpoint REST só executa a query. Inclui alternativa `$indexStats`/`$collStats` sem scheduler |
|
|
106
107
|
| Java Managers | `guides/java-managers.md` | Referência de managers Java | Métodos disponíveis em cada manager (PlayerManager, ActionManager, etc.) para uso em triggers, schedulers e public endpoints |
|
|
107
108
|
| Java Entities | `guides/java-entities.md` | Referência de entidades Java | Campos e tipos dos objetos (Player, Achievement, ActionLog, etc.) e mapeamento para coleções MongoDB |
|
|
108
109
|
| Java Libraries | `guides/java-libraries.md` | Bibliotecas e utilitários Java | JsonUtil, Guid, DateUtil, Unirest, EmailBuilder, Jongo — métodos e exemplos de uso |
|
|
@@ -124,4 +125,6 @@ Para **relatórios e queries avançadas**, consulte `guides/aggregates.md` — d
|
|
|
124
125
|
|
|
125
126
|
Para **lógica customizada com código**, consulte `guides/triggers-guide.md` (conceitos e referência de eventos) e `guides/trigger-examples.md` (exemplos Groovy prontos), além de `guides/database-access.md`.
|
|
126
127
|
|
|
128
|
+
Para **diagnosticar queries lentas ou timeout de `DELETE`** (ver se bate no índice), consulte `guides/explain-diagnostics.md` — obtém o plano real (`explain`) do MongoDB via scheduler, já que o endpoint REST só executa a query.
|
|
129
|
+
|
|
127
130
|
Para **referência completa de recursos Java** (managers, entidades, bibliotecas), consulte `guides/java-managers.md`, `guides/java-entities.md` e `guides/java-libraries.md`.
|
|
@@ -137,6 +137,8 @@ flowchart TD
|
|
|
137
137
|
|
|
138
138
|
> **Armadilha crítica (confirmada no código):** se `q` casa com **mais de 20.000** documentos, o delete **não executa** e retorna **200 OK** sem erro. O comentário no código diz "menor que 1000" (`DatabaseRest.java:466`), mas a constante real é `20_000` (`:469`) — o comentário está **desatualizado**.
|
|
139
139
|
|
|
140
|
+
> **Segunda armadilha — TIMEOUT (independente do no-op):** mesmo **abaixo** de 20.000, o delete é **síncrono num único request HTTP** e faz **3 varreduras** na coleção (`count` → `distinct(_id)` materializando **todos** os ids em memória → `remove`). Casar **milhares** de docs (ex.: ~10.000) sobre campo **não indexado** estoura o **timeout do request**. → Apague em **lotes de 1.000 por chamada, sobre campo indexado** (o número 1.000 é a intenção original do comentário do código); particione o `q` (o DELETE não aceita `$limit`) e repita até zerar. Se houver delete-trigger/audit na coleção, o lote seguro cai abaixo de 1.000. Ver §5.6 e §10.
|
|
141
|
+
|
|
140
142
|
#### Fluxograma — delete
|
|
141
143
|
|
|
142
144
|
```mermaid
|
|
@@ -507,8 +509,8 @@ Operam sobre o **banco de sistema** (`SystemFactory.getInstance()`), não o do t
|
|
|
507
509
|
### 5.5 Convenção `__c`
|
|
508
510
|
Não é validada no CRUD. Única exigência: `out` do `aggregate` deve terminar em `__c`. O framework usa internamente `csv_config__c`, `csv_log__c`, `csv_format__c`, `find_cache_<id>__c`, `email_code_verifier_log__c`.
|
|
509
511
|
|
|
510
|
-
### 5.6 Limite de delete (20.000)
|
|
511
|
-
|
|
512
|
+
### 5.6 Limite de delete (20.000) e timeout
|
|
513
|
+
**Duas armadilhas independentes** (ver §2.4): filtro que casa **> 20.000** docs → **no-op silencioso** com 200 (comentário do código diz "1000", mas o valor real é 20.000); filtro que casa **milhares ≤ 20.000** → **timeout** (operação síncrona: 3 varreduras + materializa todos os ids num único request). Mitigação: apague em **lotes de 1.000 sobre campo indexado** (§10).
|
|
512
514
|
|
|
513
515
|
### 5.7 Assimetria de encriptação — risco silencioso
|
|
514
516
|
Encriptação na escrita só ocorre se `checkReadEncryptedValues()` for verdadeiro. Esse método retorna `true` por **dois caminhos** (`AuthBean.java:221-226`): scope `read_encrypted_field_values` **ou** permissão de sistema `api/encrypted_field_values/read`. Se **nenhum** estiver presente, o dado é salvo em **plaintext** na coleção configurada, **sem aviso**. Resultado: a mesma coleção pode acumular documentos encriptados e em plaintext, dependendo de qual token escreveu. Além disso, falhas internas de `AesCrypt.encryptFields` (ex.: JSONPath inválido) são **engolidas** (`printStackTrace`) → o campo permanece plaintext silenciosamente.
|
|
@@ -562,6 +564,7 @@ Toda operação resolve `FrontController.getInstance(authBean.getApiKey()).getMa
|
|
|
562
564
|
- **PATCH parcial:** PUT é sempre replace via `save()`.
|
|
563
565
|
- **`PUT /{collection}/operations`:** **código morto** comentado em `DatabaseRest.java:393-447` (operações `$inc`/`$set`/`upsert`/`multi`). Implementado, porém **nunca exposto**.
|
|
564
566
|
- **Delete > 20.000 docs:** no-op silencioso (200).
|
|
567
|
+
- **Delete de milhares de docs (≤ 20.000):** timeout HTTP — síncrono, 3 varreduras + materializa todos os ids num único request. Apague em lotes de 1.000 sobre campo indexado (§5.6, §10).
|
|
565
568
|
- **Auditoria no bulk:** inexistente.
|
|
566
569
|
- **Encriptação no bulk e no aggregate:** inexistente (campos não são encriptados/decriptados nesses caminhos).
|
|
567
570
|
- **`aggregate_test` não oculta `password`** da coleção `player` (diferente de `/aggregate`).
|
|
@@ -625,6 +628,7 @@ GET /v3/database/cars__c/index # índices da coleção
|
|
|
625
628
|
|---|---|---|
|
|
626
629
|
| Resposta vazia com 200/201 | Token sem scope `database` ou nome de coleção curto demais | Conferir `scope` e o limiar de chars do endpoint (§5.1) |
|
|
627
630
|
| DELETE retorna 200 mas nada some | Filtro casa > 20.000 docs | `GET /count?q=...` antes; paginar o filtro |
|
|
631
|
+
| DELETE dá timeout / 504 / conexão cai | Filtro casa milhares de docs (≤ 20.000): síncrono, 3 varreduras + materializa ids | Apague em lotes de 1.000 sobre campo indexado (§5.6, §10); se houver delete-trigger/audit, lotes menores |
|
|
628
632
|
| PUT criou doc novo em vez de atualizar | `_id` ausente no body | Incluir `_id` |
|
|
629
633
|
| Campo volta como string ilegível | Token de leitura sem capacidade de cripto → ciphertext retornado | Usar token com `read_encrypted_field_values` |
|
|
630
634
|
| Coleção com mix encriptado/plaintext | Algum writer escreveu sem capacidade de cripto | Garantir capacidade em **todos** os writers (§5.7) |
|
|
@@ -687,15 +691,16 @@ PUT /v3/database/products__c
|
|
|
687
691
|
```
|
|
688
692
|
**Resultado:** cria documento NOVO (`_id` gerado). O original não é atualizado. **Sempre** envie `_id` no PUT — e lembre que campos omitidos serão removidos (replace, não patch).
|
|
689
693
|
|
|
690
|
-
### 10.5 Anti-pattern — DELETE em volume sem
|
|
694
|
+
### 10.5 Anti-pattern — DELETE em volume sem fatiar
|
|
691
695
|
```
|
|
692
696
|
DELETE /v3/database/logs__c?q=year:2022
|
|
693
697
|
```
|
|
694
|
-
|
|
698
|
+
**Duas armadilhas** (ver §2.4): se houver **> 20.000** docs de 2022, retorna **200 sem deletar nada** (no-op); se houver **milhares ≤ 20.000**, **dá timeout** (síncrono: 3 varreduras + materializa todos os ids num único request). Cheque antes e **fatie em lotes de ≤ 1.000 sobre campo indexado**:
|
|
695
699
|
```
|
|
696
|
-
GET /v3/database/count?collection=logs__c&q=year:2022
|
|
700
|
+
GET /v3/database/count?collection=logs__c&q=year:2022,month:1 # confirme ≤ 1.000
|
|
701
|
+
DELETE /v3/database/logs__c?q=year:2022,month:1 # repita por mês/dia até zerar
|
|
697
702
|
```
|
|
698
|
-
|
|
703
|
+
O DELETE REST **não aceita `$limit`** — "lote" = montar `q` que case ≤ 1.000 por chamada (janelas de data, prefixos de `_id`) e repetir. O número 1.000 vem do comentário original do código. Filtre sempre por **campo indexado** (senão as 3 varreduras continuam O(coleção)). Se a coleção inteira pode ir embora → `/drop` (cautela: sem scope). Para volume massivo sem o timeout do REST, mova a deleção para **trigger/scheduler/public endpoint** (`remove` em laço server-side).
|
|
699
704
|
|
|
700
705
|
### 10.6 Anti-pattern — confiar em encriptação sem a capacidade
|
|
701
706
|
Escrever em coleção com `crypt_object_field` configurado usando token **sem** `read_encrypted_field_values` (nem permissão de sistema) salva os campos em **plaintext silenciosamente**. Garanta a capacidade em todos os writers.
|
|
@@ -708,7 +713,7 @@ Escrever em coleção com `crypt_object_field` configurado usando token **sem**
|
|
|
708
713
|
- [ ] Nome da coleção respeita o limiar de chars do endpoint usado (§5.1): `> 3` para POST/PUT/bulk/aggregate_test, `> 2` para aggregate, `> 0` para GET-list/delete/count/index.
|
|
709
714
|
- [ ] `_id` incluído no PUT para atualizar (e ciência de que PUT é replace completo).
|
|
710
715
|
- [ ] Sufixo `__c` no destino `out` do aggregate.
|
|
711
|
-
- [ ] `GET /count` executado antes de DELETE
|
|
716
|
+
- [ ] `GET /count` executado antes de DELETE: filtro casa **≤ 20.000** (acima = no-op) **e cada chamada ≤ 1.000 sobre campo indexado** (acima = risco de timeout — fatie em lotes; §10.5).
|
|
712
717
|
- [ ] Triggers cadastradas com `entity: '<colecao>'` se há comportamento automático esperado — e ciência de que **exceção no script não aborta** a operação.
|
|
713
718
|
- [ ] `Audit` **ativo** em `{entity, event, type}` se auditoria for necessária (e token com user/player; bulk não audita).
|
|
714
719
|
- [ ] Índices criados (`POST /{collection}/index`) para campos de filtro frequente.
|