codeforge-dev 1.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/.devcontainer/.env +22 -0
- package/.devcontainer/CHANGELOG.md +197 -0
- package/.devcontainer/CLAUDE.md +117 -0
- package/.devcontainer/README.md +222 -0
- package/.devcontainer/config/main-system-prompt.md +502 -0
- package/.devcontainer/config/settings.json +47 -0
- package/.devcontainer/devcontainer.json +94 -0
- package/.devcontainer/features/README.md +113 -0
- package/.devcontainer/features/agent-browser/README.md +65 -0
- package/.devcontainer/features/agent-browser/devcontainer-feature.json +23 -0
- package/.devcontainer/features/agent-browser/install.sh +79 -0
- package/.devcontainer/features/ast-grep/README.md +24 -0
- package/.devcontainer/features/ast-grep/devcontainer-feature.json +24 -0
- package/.devcontainer/features/ast-grep/install.sh +51 -0
- package/.devcontainer/features/ccstatusline/README.md +296 -0
- package/.devcontainer/features/ccstatusline/devcontainer-feature.json +19 -0
- package/.devcontainer/features/ccstatusline/install.sh +290 -0
- package/.devcontainer/features/ccusage/README.md +205 -0
- package/.devcontainer/features/ccusage/devcontainer-feature.json +38 -0
- package/.devcontainer/features/ccusage/install.sh +132 -0
- package/.devcontainer/features/claude-code/README.md +498 -0
- package/.devcontainer/features/claude-code/config/settings.json +36 -0
- package/.devcontainer/features/claude-code/config/system-prompt.md +118 -0
- package/.devcontainer/features/claude-code/config/world-building-sp.md +1432 -0
- package/.devcontainer/features/claude-code/devcontainer-feature.json +42 -0
- package/.devcontainer/features/claude-code/install.sh +466 -0
- package/.devcontainer/features/claude-monitor/README.md +74 -0
- package/.devcontainer/features/claude-monitor/devcontainer-feature.json +38 -0
- package/.devcontainer/features/claude-monitor/install.sh +99 -0
- package/.devcontainer/features/lsp-servers/README.md +85 -0
- package/.devcontainer/features/lsp-servers/devcontainer-feature.json +40 -0
- package/.devcontainer/features/lsp-servers/install.sh +116 -0
- package/.devcontainer/features/mcp-qdrant/CHANGES.md +399 -0
- package/.devcontainer/features/mcp-qdrant/README.md +474 -0
- package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +57 -0
- package/.devcontainer/features/mcp-qdrant/install.sh +295 -0
- package/.devcontainer/features/mcp-qdrant/poststart-hook.sh +129 -0
- package/.devcontainer/features/mcp-reasoner/README.md +177 -0
- package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +20 -0
- package/.devcontainer/features/mcp-reasoner/install.sh +177 -0
- package/.devcontainer/features/mcp-reasoner/poststart-hook.sh +67 -0
- package/.devcontainer/features/notify-hook/README.md +86 -0
- package/.devcontainer/features/notify-hook/devcontainer-feature.json +23 -0
- package/.devcontainer/features/notify-hook/install.sh +38 -0
- package/.devcontainer/features/splitrail/README.md +140 -0
- package/.devcontainer/features/splitrail/devcontainer-feature.json +34 -0
- package/.devcontainer/features/splitrail/install.sh +129 -0
- package/.devcontainer/features/tree-sitter/README.md +138 -0
- package/.devcontainer/features/tree-sitter/devcontainer-feature.json +52 -0
- package/.devcontainer/features/tree-sitter/install.sh +173 -0
- package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +106 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-file.py +101 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +137 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/.claude-plugin/plugin.json +8 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/SKILL.md +387 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/cli-flags-and-output.md +312 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/sdk-and-mcp.md +569 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/SKILL.md +309 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/compose-services.md +438 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/dockerfile-patterns.md +340 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/SKILL.md +412 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/container-lifecycle.md +388 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/resources-and-security.md +444 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/SKILL.md +344 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/middleware-and-lifespan.md +254 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/pydantic-models.md +245 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/routing-and-dependencies.md +255 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/sse-and-streaming.md +318 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/SKILL.md +345 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/agents-and-tools.md +271 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/models-and-streaming.md +422 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/SKILL.md +220 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/cross-vendor-principles.md +139 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/patterns-and-antipatterns.md +376 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/skill-authoring-patterns.md +356 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/SKILL.md +329 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/advanced-queries.md +314 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/javascript-patterns.md +323 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/python-patterns.md +354 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/schema-and-pragmas.md +326 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/SKILL.md +356 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/ai-sdk-svelte.md +128 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/component-patterns.md +332 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/layercake.md +203 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/migration-guide.md +350 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/runes-and-reactivity.md +328 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/spa-and-routing.md +262 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/svelte-dnd-action.md +181 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/SKILL.md +414 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/fastapi-testing.md +411 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/svelte-testing.md +538 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codeforge-lsp/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +110 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +108 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272create-pr.md +337 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272new.md +166 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272review-commit.md +290 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272work.md +257 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/plugin.json +8 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/system-prompt.md +184 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/.claude-plugin/plugin.json +6 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/config/planning-instructions.md +14 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/functional-conjuring-map.md +989 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/hooks/hooks.json +33 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/__pycache__/post-enhance-task.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhance-planning.py +71 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-plan.sh +68 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-task.sh +120 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-plan.py +133 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-task.py +253 -0
- package/.devcontainer/scripts/setup-aliases.sh +80 -0
- package/.devcontainer/scripts/setup-config.sh +28 -0
- package/.devcontainer/scripts/setup-irie-claude.sh +32 -0
- package/.devcontainer/scripts/setup-plugins.sh +80 -0
- package/.devcontainer/scripts/setup.sh +58 -0
- package/LICENSE.txt +674 -0
- package/README.md +267 -0
- package/package.json +44 -0
- package/setup.js +83 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Advanced SQLite Queries -- Deep Dive
|
|
2
|
+
|
|
3
|
+
## 1. Common Table Expressions (CTEs)
|
|
4
|
+
|
|
5
|
+
CTEs define named temporary result sets within a single query. They improve readability and allow recursive queries.
|
|
6
|
+
|
|
7
|
+
### Non-Recursive CTEs
|
|
8
|
+
|
|
9
|
+
```sql
|
|
10
|
+
WITH active_users AS (
|
|
11
|
+
SELECT id, email, display_name
|
|
12
|
+
FROM users
|
|
13
|
+
WHERE last_login > date('now', '-30 days')
|
|
14
|
+
),
|
|
15
|
+
user_stats AS (
|
|
16
|
+
SELECT user_id, COUNT(*) AS post_count
|
|
17
|
+
FROM posts
|
|
18
|
+
GROUP BY user_id
|
|
19
|
+
)
|
|
20
|
+
SELECT au.email, au.display_name, COALESCE(us.post_count, 0) AS post_count
|
|
21
|
+
FROM active_users au
|
|
22
|
+
LEFT JOIN user_stats us ON au.id = us.user_id
|
|
23
|
+
ORDER BY post_count DESC;
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Recursive CTEs
|
|
27
|
+
|
|
28
|
+
Recursive CTEs self-reference to traverse hierarchical data:
|
|
29
|
+
|
|
30
|
+
```sql
|
|
31
|
+
-- Organizational hierarchy
|
|
32
|
+
WITH RECURSIVE org_tree AS (
|
|
33
|
+
-- Base case: root nodes (no manager)
|
|
34
|
+
SELECT id, name, manager_id, 0 AS depth, name AS path
|
|
35
|
+
FROM employees
|
|
36
|
+
WHERE manager_id IS NULL
|
|
37
|
+
|
|
38
|
+
UNION ALL
|
|
39
|
+
|
|
40
|
+
-- Recursive case: children of already-found nodes
|
|
41
|
+
SELECT e.id, e.name, e.manager_id, ot.depth + 1,
|
|
42
|
+
ot.path || ' > ' || e.name
|
|
43
|
+
FROM employees e
|
|
44
|
+
JOIN org_tree ot ON e.manager_id = ot.id
|
|
45
|
+
)
|
|
46
|
+
SELECT * FROM org_tree ORDER BY path;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```sql
|
|
50
|
+
-- Bill of materials (tree of components)
|
|
51
|
+
WITH RECURSIVE bom AS (
|
|
52
|
+
SELECT component_id, parent_id, quantity, 1 AS level
|
|
53
|
+
FROM components
|
|
54
|
+
WHERE parent_id = :root_id
|
|
55
|
+
|
|
56
|
+
UNION ALL
|
|
57
|
+
|
|
58
|
+
SELECT c.component_id, c.parent_id, c.quantity * bom.quantity, bom.level + 1
|
|
59
|
+
FROM components c
|
|
60
|
+
JOIN bom ON c.parent_id = bom.component_id
|
|
61
|
+
)
|
|
62
|
+
SELECT * FROM bom;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Limit Recursion Depth
|
|
66
|
+
|
|
67
|
+
SQLite defaults to a maximum recursion depth of 1000. Override with `LIMIT` on the CTE or adjust `SQLITE_MAX_VARIABLE_NUMBER`:
|
|
68
|
+
|
|
69
|
+
```sql
|
|
70
|
+
WITH RECURSIVE seq(n) AS (
|
|
71
|
+
SELECT 1
|
|
72
|
+
UNION ALL
|
|
73
|
+
SELECT n + 1 FROM seq WHERE n < 100
|
|
74
|
+
)
|
|
75
|
+
SELECT n FROM seq;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 2. Window Functions
|
|
81
|
+
|
|
82
|
+
Window functions compute values across a set of rows related to the current row, without collapsing the result set.
|
|
83
|
+
|
|
84
|
+
### ROW_NUMBER, RANK, DENSE_RANK
|
|
85
|
+
|
|
86
|
+
```sql
|
|
87
|
+
-- Rank users by post count within each category
|
|
88
|
+
SELECT
|
|
89
|
+
u.name,
|
|
90
|
+
c.category_name,
|
|
91
|
+
COUNT(p.id) AS post_count,
|
|
92
|
+
ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY COUNT(p.id) DESC) AS row_num,
|
|
93
|
+
RANK() OVER (PARTITION BY c.id ORDER BY COUNT(p.id) DESC) AS rank,
|
|
94
|
+
DENSE_RANK() OVER (PARTITION BY c.id ORDER BY COUNT(p.id) DESC) AS dense_rank
|
|
95
|
+
FROM users u
|
|
96
|
+
JOIN posts p ON u.id = p.user_id
|
|
97
|
+
JOIN categories c ON p.category_id = c.id
|
|
98
|
+
GROUP BY u.id, c.id;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
| Function | Ties | Gaps |
|
|
102
|
+
|----------|------|------|
|
|
103
|
+
| `ROW_NUMBER` | Breaks ties arbitrarily | No gaps |
|
|
104
|
+
| `RANK` | Same rank for ties | Gaps after ties |
|
|
105
|
+
| `DENSE_RANK` | Same rank for ties | No gaps |
|
|
106
|
+
|
|
107
|
+
### LAG and LEAD
|
|
108
|
+
|
|
109
|
+
Access previous or next row values without a self-join:
|
|
110
|
+
|
|
111
|
+
```sql
|
|
112
|
+
-- Compare each sale to the previous day
|
|
113
|
+
SELECT
|
|
114
|
+
date,
|
|
115
|
+
revenue,
|
|
116
|
+
LAG(revenue, 1) OVER (ORDER BY date) AS prev_day_revenue,
|
|
117
|
+
revenue - LAG(revenue, 1) OVER (ORDER BY date) AS daily_change,
|
|
118
|
+
LEAD(revenue, 1) OVER (ORDER BY date) AS next_day_revenue
|
|
119
|
+
FROM daily_sales;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Running Totals and Moving Averages
|
|
123
|
+
|
|
124
|
+
```sql
|
|
125
|
+
-- Running total of revenue
|
|
126
|
+
SELECT
|
|
127
|
+
date,
|
|
128
|
+
revenue,
|
|
129
|
+
SUM(revenue) OVER (ORDER BY date ROWS UNBOUNDED PRECEDING) AS running_total
|
|
130
|
+
FROM daily_sales;
|
|
131
|
+
|
|
132
|
+
-- 7-day moving average
|
|
133
|
+
SELECT
|
|
134
|
+
date,
|
|
135
|
+
revenue,
|
|
136
|
+
AVG(revenue) OVER (
|
|
137
|
+
ORDER BY date
|
|
138
|
+
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
|
|
139
|
+
) AS moving_avg_7d
|
|
140
|
+
FROM daily_sales;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### NTILE and Percentiles
|
|
144
|
+
|
|
145
|
+
```sql
|
|
146
|
+
-- Divide users into quartiles by score
|
|
147
|
+
SELECT
|
|
148
|
+
name,
|
|
149
|
+
score,
|
|
150
|
+
NTILE(4) OVER (ORDER BY score DESC) AS quartile
|
|
151
|
+
FROM users;
|
|
152
|
+
|
|
153
|
+
-- First and last value in partition
|
|
154
|
+
SELECT
|
|
155
|
+
department,
|
|
156
|
+
name,
|
|
157
|
+
salary,
|
|
158
|
+
FIRST_VALUE(name) OVER (PARTITION BY department ORDER BY salary DESC) AS highest_paid,
|
|
159
|
+
LAST_VALUE(name) OVER (
|
|
160
|
+
PARTITION BY department ORDER BY salary DESC
|
|
161
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
162
|
+
) AS lowest_paid
|
|
163
|
+
FROM employees;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 3. Upsert (INSERT ... ON CONFLICT)
|
|
169
|
+
|
|
170
|
+
Insert a row or update it if a unique constraint would be violated:
|
|
171
|
+
|
|
172
|
+
```sql
|
|
173
|
+
-- Update on conflict with specific columns
|
|
174
|
+
INSERT INTO settings (key, value, updated_at)
|
|
175
|
+
VALUES ('theme', 'dark', strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
176
|
+
ON CONFLICT (key) DO UPDATE SET
|
|
177
|
+
value = excluded.value,
|
|
178
|
+
updated_at = excluded.updated_at;
|
|
179
|
+
|
|
180
|
+
-- Ignore duplicates silently
|
|
181
|
+
INSERT INTO tags (name) VALUES ('sqlite')
|
|
182
|
+
ON CONFLICT (name) DO NOTHING;
|
|
183
|
+
|
|
184
|
+
-- Conditional upsert
|
|
185
|
+
INSERT INTO counters (key, count) VALUES ('visits', 1)
|
|
186
|
+
ON CONFLICT (key) DO UPDATE SET count = count + 1
|
|
187
|
+
WHERE count < 1000000;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`excluded` refers to the row that would have been inserted. Use it to reference the new values in the `DO UPDATE` clause.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 4. RETURNING
|
|
195
|
+
|
|
196
|
+
`RETURNING` retrieves the affected rows from `INSERT`, `UPDATE`, or `DELETE` without a separate query:
|
|
197
|
+
|
|
198
|
+
```sql
|
|
199
|
+
-- Get the inserted row with auto-generated id
|
|
200
|
+
INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice')
|
|
201
|
+
RETURNING id, email, name, created_at;
|
|
202
|
+
|
|
203
|
+
-- Get old values during update
|
|
204
|
+
UPDATE products SET price = price * 1.1
|
|
205
|
+
WHERE category = 'electronics'
|
|
206
|
+
RETURNING id, name, price AS new_price;
|
|
207
|
+
|
|
208
|
+
-- Confirm what was deleted
|
|
209
|
+
DELETE FROM sessions WHERE expires_at < datetime('now')
|
|
210
|
+
RETURNING id, user_id;
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`RETURNING` is particularly useful with `INSERT` to avoid a round-trip `SELECT` for the auto-generated `rowid`.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 5. EXPLAIN QUERY PLAN
|
|
218
|
+
|
|
219
|
+
Analyze how SQLite executes a query to identify missing indexes or inefficient scans:
|
|
220
|
+
|
|
221
|
+
```sql
|
|
222
|
+
EXPLAIN QUERY PLAN
|
|
223
|
+
SELECT u.name, COUNT(p.id)
|
|
224
|
+
FROM users u
|
|
225
|
+
JOIN posts p ON u.id = p.user_id
|
|
226
|
+
WHERE u.created_at > '2024-01-01'
|
|
227
|
+
GROUP BY u.id;
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Output interpretation:
|
|
231
|
+
|
|
232
|
+
| Term | Meaning |
|
|
233
|
+
|------|---------|
|
|
234
|
+
| `SCAN` | Full table scan -- no index used |
|
|
235
|
+
| `SEARCH` | Index lookup -- efficient |
|
|
236
|
+
| `USING INDEX` | Specifies which index |
|
|
237
|
+
| `USING COVERING INDEX` | Index contains all needed columns, no table access |
|
|
238
|
+
| `TEMP B-TREE` | Temporary sort/group structure |
|
|
239
|
+
|
|
240
|
+
### Optimization Workflow
|
|
241
|
+
|
|
242
|
+
1. Run `EXPLAIN QUERY PLAN` on slow queries.
|
|
243
|
+
2. Look for `SCAN` on large tables -- this indicates a missing index.
|
|
244
|
+
3. Add an index on the filtered/joined column.
|
|
245
|
+
4. Re-run `EXPLAIN QUERY PLAN` to verify `SEARCH USING INDEX`.
|
|
246
|
+
5. Run `ANALYZE` after adding indexes to update the query planner's statistics.
|
|
247
|
+
|
|
248
|
+
```sql
|
|
249
|
+
-- Before: SCAN users
|
|
250
|
+
EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = 'alice@example.com';
|
|
251
|
+
|
|
252
|
+
-- Add index
|
|
253
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
254
|
+
|
|
255
|
+
-- After: SEARCH users USING INDEX idx_users_email
|
|
256
|
+
EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = 'alice@example.com';
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 6. Covering Indexes
|
|
262
|
+
|
|
263
|
+
A covering index includes all columns referenced by a query, eliminating the need to read the main table:
|
|
264
|
+
|
|
265
|
+
```sql
|
|
266
|
+
-- This query needs id, email, and display_name
|
|
267
|
+
SELECT id, email, display_name FROM users WHERE email LIKE 'a%';
|
|
268
|
+
|
|
269
|
+
-- Covering index for this query
|
|
270
|
+
CREATE INDEX idx_users_email_covering ON users(email, id, display_name);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
After adding the covering index, `EXPLAIN QUERY PLAN` shows `USING COVERING INDEX` instead of `USING INDEX` followed by a table lookup.
|
|
274
|
+
|
|
275
|
+
### When to Use Covering Indexes
|
|
276
|
+
|
|
277
|
+
- High-frequency read queries with a known column set.
|
|
278
|
+
- Queries where the table is wide (many columns) but only a few are needed.
|
|
279
|
+
- The tradeoff is increased write overhead and storage for the index.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 7. Partial and Expression Indexes
|
|
284
|
+
|
|
285
|
+
### Partial Indexes
|
|
286
|
+
|
|
287
|
+
Index only rows matching a condition, reducing index size and write overhead:
|
|
288
|
+
|
|
289
|
+
```sql
|
|
290
|
+
-- Index only active users (smaller, faster)
|
|
291
|
+
CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';
|
|
292
|
+
|
|
293
|
+
-- Index only non-null values
|
|
294
|
+
CREATE INDEX idx_users_display_name ON users(display_name)
|
|
295
|
+
WHERE display_name IS NOT NULL;
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The query planner uses a partial index only when the `WHERE` clause of the query matches the index condition.
|
|
299
|
+
|
|
300
|
+
### Expression Indexes
|
|
301
|
+
|
|
302
|
+
Index computed values:
|
|
303
|
+
|
|
304
|
+
```sql
|
|
305
|
+
-- Case-insensitive email lookup
|
|
306
|
+
CREATE INDEX idx_users_email_lower ON users(lower(email));
|
|
307
|
+
-- SELECT * FROM users WHERE lower(email) = 'alice@example.com';
|
|
308
|
+
|
|
309
|
+
-- Date extraction from ISO timestamp
|
|
310
|
+
CREATE INDEX idx_posts_date ON posts(date(created_at));
|
|
311
|
+
-- SELECT * FROM posts WHERE date(created_at) = '2024-06-15';
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Expression indexes work with any deterministic SQL expression. The query must use the exact same expression for the planner to select the index.
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# JavaScript SQLite Patterns -- Deep Dive
|
|
2
|
+
|
|
3
|
+
## 1. better-sqlite3 User-Defined Functions
|
|
4
|
+
|
|
5
|
+
### Scalar Functions
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const Database = require("better-sqlite3");
|
|
9
|
+
const db = new Database("app.db");
|
|
10
|
+
|
|
11
|
+
db.function("reverse", (str) => str.split("").reverse().join(""));
|
|
12
|
+
// SELECT reverse(name) FROM users;
|
|
13
|
+
|
|
14
|
+
db.function("json_arr_len", (json) => {
|
|
15
|
+
if (json === null) return 0;
|
|
16
|
+
return JSON.parse(json).length;
|
|
17
|
+
});
|
|
18
|
+
// SELECT json_arr_len(tags) FROM posts;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Aggregate Functions
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
db.aggregate("median", {
|
|
25
|
+
start: () => [],
|
|
26
|
+
step: (arr, value) => {
|
|
27
|
+
if (value !== null) arr.push(value);
|
|
28
|
+
return arr;
|
|
29
|
+
},
|
|
30
|
+
result: (arr) => {
|
|
31
|
+
if (arr.length === 0) return null;
|
|
32
|
+
arr.sort((a, b) => a - b);
|
|
33
|
+
const mid = Math.floor(arr.length / 2);
|
|
34
|
+
return arr.length % 2 === 0
|
|
35
|
+
? (arr[mid - 1] + arr[mid]) / 2
|
|
36
|
+
: arr[mid];
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
// SELECT median(price) FROM products;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Table-Valued Functions
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
db.table("generate_series", {
|
|
46
|
+
columns: ["value"],
|
|
47
|
+
parameters: ["start", "stop", "step"],
|
|
48
|
+
*rows(start, stop, step = 1) {
|
|
49
|
+
for (let i = start; i <= stop; i += step) {
|
|
50
|
+
yield { value: i };
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
// SELECT value FROM generate_series(1, 10, 2);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 2. WAL Checkpoints
|
|
60
|
+
|
|
61
|
+
WAL files grow until checkpointed. better-sqlite3 enables automatic checkpointing by default, but manual control is available:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
// Check WAL size
|
|
65
|
+
const walInfo = db.pragma("wal_checkpoint(PASSIVE)");
|
|
66
|
+
// Returns: { busy: 0, checkpointed: N, log: N }
|
|
67
|
+
|
|
68
|
+
// Force a full checkpoint (waits for readers)
|
|
69
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Checkpoint Strategies
|
|
73
|
+
|
|
74
|
+
| Mode | Behavior | Use Case |
|
|
75
|
+
|------|----------|----------|
|
|
76
|
+
| `PASSIVE` | Checkpoint pages not locked by readers | Background maintenance |
|
|
77
|
+
| `FULL` | Wait for readers, then checkpoint all pages | Before backup |
|
|
78
|
+
| `TRUNCATE` | Like FULL, then truncate WAL to zero | Reduce disk usage |
|
|
79
|
+
| `RESTART` | Like FULL, then reset WAL to beginning | Reclaim WAL file space |
|
|
80
|
+
|
|
81
|
+
Schedule periodic `PASSIVE` checkpoints in long-running applications. Use `TRUNCATE` before backup or deployment.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 3. Prepared Statement Patterns
|
|
86
|
+
|
|
87
|
+
### Caching Prepared Statements
|
|
88
|
+
|
|
89
|
+
better-sqlite3 `prepare()` compiles the SQL once. Store references for reuse:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
class UserRepository {
|
|
93
|
+
constructor(db) {
|
|
94
|
+
this.db = db;
|
|
95
|
+
this.stmts = {
|
|
96
|
+
getById: db.prepare("SELECT * FROM users WHERE id = ?"),
|
|
97
|
+
getByEmail: db.prepare("SELECT * FROM users WHERE email = ?"),
|
|
98
|
+
insert: db.prepare(
|
|
99
|
+
"INSERT INTO users (email, name) VALUES (@email, @name) RETURNING *"
|
|
100
|
+
),
|
|
101
|
+
update: db.prepare(
|
|
102
|
+
"UPDATE users SET name = @name WHERE id = @id RETURNING *"
|
|
103
|
+
),
|
|
104
|
+
delete: db.prepare("DELETE FROM users WHERE id = ?"),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getById(id) {
|
|
109
|
+
return this.stmts.getById.get(id);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getByEmail(email) {
|
|
113
|
+
return this.stmts.getByEmail.get(email);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
create(data) {
|
|
117
|
+
return this.stmts.insert.get(data);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
update(id, data) {
|
|
121
|
+
return this.stmts.update.get({ ...data, id });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
delete(id) {
|
|
125
|
+
return this.stmts.delete.run(id);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Iterate vs All
|
|
131
|
+
|
|
132
|
+
Use `.iterate()` for large result sets to avoid loading everything into memory:
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
const stmt = db.prepare("SELECT * FROM logs WHERE date > ?");
|
|
136
|
+
|
|
137
|
+
// All at once (small result sets)
|
|
138
|
+
const rows = stmt.all("2024-01-01");
|
|
139
|
+
|
|
140
|
+
// Iterator (large result sets)
|
|
141
|
+
for (const row of stmt.iterate("2024-01-01")) {
|
|
142
|
+
processRow(row);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 4. Transaction Patterns
|
|
149
|
+
|
|
150
|
+
### Basic Transactions
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
const transferFunds = db.transaction((fromId, toId, amount) => {
|
|
154
|
+
const from = db.prepare("SELECT balance FROM accounts WHERE id = ?").get(fromId);
|
|
155
|
+
if (from.balance < amount) {
|
|
156
|
+
throw new Error("Insufficient funds");
|
|
157
|
+
}
|
|
158
|
+
db.prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?").run(amount, fromId);
|
|
159
|
+
db.prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?").run(amount, toId);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
transferFunds(1, 2, 100);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The `db.transaction()` wrapper automatically commits on success and rolls back on error. Nested `db.transaction()` calls use savepoints.
|
|
166
|
+
|
|
167
|
+
### Deferred vs Immediate
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
const immediateTransaction = db.transaction(() => {
|
|
171
|
+
// Acquires write lock immediately
|
|
172
|
+
db.prepare("INSERT INTO logs (msg) VALUES (?)").run("action");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Force IMMEDIATE mode for write transactions
|
|
176
|
+
immediateTransaction.immediate();
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Use `.immediate()` when the transaction will write, to avoid SQLITE_BUSY errors from lock promotion.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 5. Cloudflare D1 Patterns
|
|
184
|
+
|
|
185
|
+
### Batch Operations
|
|
186
|
+
|
|
187
|
+
D1 batches execute all statements in a single round-trip as an implicit transaction:
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
export default {
|
|
191
|
+
async fetch(request, env) {
|
|
192
|
+
const results = await env.DB.batch([
|
|
193
|
+
env.DB.prepare("INSERT INTO users (email) VALUES (?)").bind("alice@example.com"),
|
|
194
|
+
env.DB.prepare("INSERT INTO profiles (user_id, bio) VALUES (last_insert_rowid(), ?)").bind("Hello"),
|
|
195
|
+
env.DB.prepare("SELECT * FROM users WHERE email = ?").bind("alice@example.com"),
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
const user = results[2].results[0];
|
|
199
|
+
return Response.json(user);
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### D1 Query Helpers
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
// .first() -- single row or null
|
|
208
|
+
const user = await env.DB.prepare("SELECT * FROM users WHERE id = ?")
|
|
209
|
+
.bind(id)
|
|
210
|
+
.first();
|
|
211
|
+
|
|
212
|
+
// .all() -- all rows with metadata
|
|
213
|
+
const { results, meta } = await env.DB.prepare("SELECT * FROM users").all();
|
|
214
|
+
// meta: { duration, rows_read, rows_written, changes }
|
|
215
|
+
|
|
216
|
+
// .raw() -- array of arrays (no column names)
|
|
217
|
+
const rows = await env.DB.prepare("SELECT id, email FROM users").raw();
|
|
218
|
+
// [[1, "alice@example.com"], [2, "bob@example.com"]]
|
|
219
|
+
|
|
220
|
+
// .run() -- for INSERT/UPDATE/DELETE
|
|
221
|
+
const { meta } = await env.DB.prepare("DELETE FROM sessions WHERE expired < ?")
|
|
222
|
+
.bind(Date.now())
|
|
223
|
+
.run();
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 6. D1 Migrations
|
|
229
|
+
|
|
230
|
+
### File-Based Migrations
|
|
231
|
+
|
|
232
|
+
D1 uses numbered SQL files in a `migrations/` directory:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
migrations/
|
|
236
|
+
├── 0001_create_users.sql
|
|
237
|
+
├── 0002_add_posts.sql
|
|
238
|
+
└── 0003_add_fts.sql
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```sql
|
|
242
|
+
-- 0001_create_users.sql
|
|
243
|
+
CREATE TABLE users (
|
|
244
|
+
id INTEGER PRIMARY KEY,
|
|
245
|
+
email TEXT NOT NULL UNIQUE,
|
|
246
|
+
name TEXT,
|
|
247
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
248
|
+
);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Apply migrations with Wrangler:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
npx wrangler d1 migrations apply my-database # production
|
|
255
|
+
npx wrangler d1 migrations apply my-database --local # local dev
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Migration Best Practices
|
|
259
|
+
|
|
260
|
+
- Each migration file should be idempotent where possible (use `IF NOT EXISTS`).
|
|
261
|
+
- Never modify an already-applied migration -- create a new one.
|
|
262
|
+
- Test migrations locally before applying to production.
|
|
263
|
+
- D1 tracks applied migrations automatically; manual version tables are unnecessary.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 7. Testing with Miniflare
|
|
268
|
+
|
|
269
|
+
Miniflare provides a local D1 simulator for testing Workers without deploying:
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
import { Miniflare } from "miniflare";
|
|
273
|
+
|
|
274
|
+
const mf = new Miniflare({
|
|
275
|
+
modules: true,
|
|
276
|
+
script: `export default { async fetch(request, env) { return new Response("ok"); } }`,
|
|
277
|
+
d1Databases: ["DB"],
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const db = await mf.getD1Database("DB");
|
|
281
|
+
|
|
282
|
+
// Run migrations
|
|
283
|
+
await db.exec(`
|
|
284
|
+
CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL);
|
|
285
|
+
`);
|
|
286
|
+
|
|
287
|
+
// Test queries
|
|
288
|
+
await db.prepare("INSERT INTO users (email) VALUES (?)").bind("test@example.com").run();
|
|
289
|
+
const user = await db.prepare("SELECT * FROM users WHERE email = ?")
|
|
290
|
+
.bind("test@example.com")
|
|
291
|
+
.first();
|
|
292
|
+
console.assert(user.email === "test@example.com");
|
|
293
|
+
|
|
294
|
+
await mf.dispose();
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Integration Test Pattern
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
import { describe, it, beforeEach, afterAll } from "vitest";
|
|
301
|
+
import { Miniflare } from "miniflare";
|
|
302
|
+
|
|
303
|
+
describe("User API", () => {
|
|
304
|
+
let mf;
|
|
305
|
+
let db;
|
|
306
|
+
|
|
307
|
+
beforeEach(async () => {
|
|
308
|
+
mf = new Miniflare({ /* config */ });
|
|
309
|
+
db = await mf.getD1Database("DB");
|
|
310
|
+
await db.exec(SCHEMA_SQL);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
afterAll(async () => {
|
|
314
|
+
await mf?.dispose();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("creates a user", async () => {
|
|
318
|
+
await db.prepare("INSERT INTO users (email) VALUES (?)").bind("a@b.com").run();
|
|
319
|
+
const user = await db.prepare("SELECT * FROM users").first();
|
|
320
|
+
expect(user.email).toBe("a@b.com");
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
```
|