@jgamaraalv/ts-dev-kit 1.0.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/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/agents/accessibility-pro.md +139 -0
- package/agents/api-builder.md +110 -0
- package/agents/code-reviewer.md +190 -0
- package/agents/database-expert.md +138 -0
- package/agents/debugger.md +241 -0
- package/agents/docker-expert.md +51 -0
- package/agents/multi-agent-coordinator.md +378 -0
- package/agents/nextjs-expert.md +136 -0
- package/agents/performance-engineer.md +138 -0
- package/agents/playwright-expert.md +126 -0
- package/agents/react-specialist.md +97 -0
- package/agents/security-scanner.md +105 -0
- package/agents/test-generator.md +221 -0
- package/agents/typescript-pro.md +253 -0
- package/agents/ux-optimizer.md +93 -0
- package/docs/rules/orchestration.md.template +126 -0
- package/package.json +28 -0
- package/skills/bullmq/SKILL.md +225 -0
- package/skills/bullmq/references/flows-and-schedulers.md +186 -0
- package/skills/bullmq/references/job-types-and-options.md +163 -0
- package/skills/bullmq/references/patterns.md +273 -0
- package/skills/bullmq/references/production.md +308 -0
- package/skills/composition-patterns/SKILL.md +58 -0
- package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
- package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
- package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
- package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
- package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
- package/skills/composition-patterns/references/state-context-interface.md +194 -0
- package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
- package/skills/composition-patterns/references/state-lift-state.md +126 -0
- package/skills/conventional-commits/SKILL.md +148 -0
- package/skills/docker/SKILL.md +55 -0
- package/skills/docker/references/compose-configs.md +95 -0
- package/skills/docker/references/monorepo-dockerfile.md +111 -0
- package/skills/drizzle-pg/SKILL.md +202 -0
- package/skills/drizzle-pg/references/advanced.md +299 -0
- package/skills/drizzle-pg/references/migrations.md +214 -0
- package/skills/drizzle-pg/references/queries.md +321 -0
- package/skills/drizzle-pg/references/relations.md +272 -0
- package/skills/drizzle-pg/references/schema-pg.md +256 -0
- package/skills/drizzle-pg/references/sql-operator.md +215 -0
- package/skills/fastify-best-practices/SKILL.md +143 -0
- package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
- package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
- package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
- package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
- package/skills/fastify-best-practices/references/server-and-options.md +127 -0
- package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
- package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
- package/skills/ioredis/SKILL.md +51 -0
- package/skills/ioredis/references/advanced-patterns.md +312 -0
- package/skills/ioredis/references/cluster-sentinel.md +280 -0
- package/skills/ioredis/references/connection-options.md +187 -0
- package/skills/ioredis/references/core-api.md +179 -0
- package/skills/nextjs-best-practices/SKILL.md +194 -0
- package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
- package/skills/nextjs-best-practices/references/bundling.md +192 -0
- package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
- package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
- package/skills/nextjs-best-practices/references/directives.md +74 -0
- package/skills/nextjs-best-practices/references/error-handling.md +237 -0
- package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
- package/skills/nextjs-best-practices/references/font.md +175 -0
- package/skills/nextjs-best-practices/references/functions.md +116 -0
- package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
- package/skills/nextjs-best-practices/references/image.md +184 -0
- package/skills/nextjs-best-practices/references/metadata.md +305 -0
- package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
- package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
- package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
- package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
- package/skills/nextjs-best-practices/references/scripts.md +148 -0
- package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
- package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
- package/skills/owasp-security-review/SKILL.md +98 -0
- package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
- package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
- package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
- package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
- package/skills/owasp-security-review/references/a05-injection.md +106 -0
- package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
- package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
- package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
- package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
- package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
- package/skills/postgresql/SKILL.md +50 -0
- package/skills/postgresql/references/ddl-schema.md +300 -0
- package/skills/postgresql/references/indexes.md +257 -0
- package/skills/postgresql/references/jsonb.md +261 -0
- package/skills/postgresql/references/performance.md +291 -0
- package/skills/postgresql/references/psql-cli.md +153 -0
- package/skills/postgresql/references/queries.md +287 -0
- package/skills/postgresql/references/transactions.md +280 -0
- package/skills/react-best-practices/SKILL.md +110 -0
- package/skills/react-best-practices/references/advanced-patterns.md +91 -0
- package/skills/react-best-practices/references/async-patterns.md +233 -0
- package/skills/react-best-practices/references/bundle-optimization.md +201 -0
- package/skills/react-best-practices/references/client-patterns.md +178 -0
- package/skills/react-best-practices/references/js-performance.md +210 -0
- package/skills/react-best-practices/references/rendering-performance.md +209 -0
- package/skills/react-best-practices/references/rerender-optimization.md +316 -0
- package/skills/react-best-practices/references/server-performance.md +274 -0
- package/skills/service-worker/SKILL.md +195 -0
- package/skills/service-worker/references/api-reference.md +114 -0
- package/skills/service-worker/references/caching-strategies.md +202 -0
- package/skills/service-worker/references/push-and-sync.md +261 -0
- package/skills/typescript-conventions/SKILL.md +51 -0
- package/skills/ui-ux-guidelines/SKILL.md +105 -0
- package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
- package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
- package/skills/ui-ux-guidelines/references/layout-typography-animation.md +95 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# PostgreSQL Indexes Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Index type decision guide](#decision-guide)
|
|
6
|
+
2. [B-tree](#b-tree)
|
|
7
|
+
3. [Hash](#hash)
|
|
8
|
+
4. [GIN](#gin)
|
|
9
|
+
5. [GiST](#gist)
|
|
10
|
+
6. [BRIN](#brin)
|
|
11
|
+
7. [Partial indexes](#partial-indexes)
|
|
12
|
+
8. [Expression indexes](#expression-indexes)
|
|
13
|
+
9. [Multicolumn indexes](#multicolumn-indexes)
|
|
14
|
+
10. [Index creation options](#creation-options)
|
|
15
|
+
11. [Inspecting indexes](#inspecting)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Decision guide
|
|
20
|
+
|
|
21
|
+
| Situation | Use |
|
|
22
|
+
| ---------------------------------------------------------------- | -------------------------------------------------- |
|
|
23
|
+
| Equality / range on any sortable type (int, text, date, numeric) | **B-tree** (default) |
|
|
24
|
+
| Equality-only on large values where hash fits in memory | **Hash** |
|
|
25
|
+
| `LIKE 'foo%'` prefix match | **B-tree** with `text_pattern_ops` if non-C locale |
|
|
26
|
+
| `LIKE '%foo'` suffix / `LIKE '%foo%'` | **GIN** with `pg_trgm` extension |
|
|
27
|
+
| Full-text search (`@@` tsvector) | **GIN** |
|
|
28
|
+
| Array containment (`@>`, `<@`, `&&`) | **GIN** |
|
|
29
|
+
| JSONB containment / key existence | **GIN** |
|
|
30
|
+
| Geometric / PostGIS spatial queries | **GiST** |
|
|
31
|
+
| Nearest-neighbor (`ORDER BY location <-> point`) | **GiST** |
|
|
32
|
+
| `tsvector` (alternative to GIN, smaller, faster build) | **GiST** |
|
|
33
|
+
| Range types (`int4range`, `tstzrange`) | **GiST** |
|
|
34
|
+
| Append-only / time-series, very large tables, loose range filter | **BRIN** |
|
|
35
|
+
| Index only non-null or subset of rows | **Partial index** |
|
|
36
|
+
| Index on expression / function result | **Expression index** |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## B-tree
|
|
41
|
+
|
|
42
|
+
Default type. Use for: `<`, `<=`, `=`, `>=`, `>`, `BETWEEN`, `IN`, `IS NULL`, `IS NOT NULL`, `LIKE 'prefix%'`.
|
|
43
|
+
|
|
44
|
+
```sql
|
|
45
|
+
-- Basic
|
|
46
|
+
CREATE INDEX idx_users_email ON users (email);
|
|
47
|
+
|
|
48
|
+
-- Descending (useful when queries ORDER BY col DESC)
|
|
49
|
+
CREATE INDEX idx_orders_created_desc ON orders (created_at DESC);
|
|
50
|
+
|
|
51
|
+
-- Include (covering index — avoid heap fetch)
|
|
52
|
+
CREATE INDEX idx_orders_customer ON orders (customer_id) INCLUDE (status, total);
|
|
53
|
+
|
|
54
|
+
-- Pattern matching (non-C locale requires operator class)
|
|
55
|
+
CREATE INDEX idx_users_name_pattern ON users (name text_pattern_ops);
|
|
56
|
+
-- Then: WHERE name LIKE 'Alice%' uses this index
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
B-tree index can support `ORDER BY` without a sort step if the index column order matches.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Hash
|
|
64
|
+
|
|
65
|
+
Only for `=` comparisons. Faster than B-tree for pure equality on large keys, but cannot support range queries or sorting.
|
|
66
|
+
|
|
67
|
+
```sql
|
|
68
|
+
CREATE INDEX idx_sessions_token ON sessions USING HASH (token);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Note: Hash indexes are WAL-logged (crash-safe) since PostgreSQL 10.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## GIN (Generalized Inverted Index)
|
|
76
|
+
|
|
77
|
+
Best for multi-valued columns: arrays, JSONB, full-text (`tsvector`). Slower to build and update than B-tree; query-time is faster than GiST for containment checks.
|
|
78
|
+
|
|
79
|
+
```sql
|
|
80
|
+
-- Full-text search
|
|
81
|
+
CREATE INDEX idx_posts_search ON posts USING GIN (to_tsvector('portuguese', content));
|
|
82
|
+
|
|
83
|
+
-- JSONB — supports @>, ?, ?|, ?&
|
|
84
|
+
CREATE INDEX idx_profiles_data ON profiles USING GIN (data);
|
|
85
|
+
-- Specific path (jsonb_path_ops — smaller, only @> operator)
|
|
86
|
+
CREATE INDEX idx_profiles_data_path ON profiles USING GIN (data jsonb_path_ops);
|
|
87
|
+
|
|
88
|
+
-- Array containment
|
|
89
|
+
CREATE INDEX idx_products_tags ON products USING GIN (tags);
|
|
90
|
+
|
|
91
|
+
-- Trigram (requires pg_trgm extension) — enables LIKE '%foo%', ILIKE, similarity
|
|
92
|
+
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
|
93
|
+
CREATE INDEX idx_users_name_trgm ON users USING GIN (name gin_trgm_ops);
|
|
94
|
+
-- Now: WHERE name ILIKE '%alice%' uses the index
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
GIN vs `jsonb_path_ops`:
|
|
98
|
+
|
|
99
|
+
- Default `jsonb_ops`: supports `@>`, `?`, `?|`, `?&`
|
|
100
|
+
- `jsonb_path_ops`: supports only `@>` but index is smaller and faster
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## GiST (Generalized Search Tree)
|
|
105
|
+
|
|
106
|
+
Infrastructure for custom index types. Built-in support for: geometric types, `tsvector`, range types, PostGIS geometry.
|
|
107
|
+
|
|
108
|
+
```sql
|
|
109
|
+
-- Geometric nearest-neighbor
|
|
110
|
+
CREATE INDEX idx_locations_point ON locations USING GIST (coordinates);
|
|
111
|
+
-- Query: SELECT * FROM locations ORDER BY coordinates <-> '(0,0)'::point LIMIT 10;
|
|
112
|
+
|
|
113
|
+
-- Range type (no overlap)
|
|
114
|
+
CREATE INDEX idx_reservations_period ON reservations USING GIST (period);
|
|
115
|
+
-- Query: WHERE period && '[2025-01-01, 2025-01-07)'::daterange
|
|
116
|
+
|
|
117
|
+
-- Full-text (alternative to GIN — smaller index, slower search)
|
|
118
|
+
CREATE INDEX idx_posts_fts ON posts USING GIST (to_tsvector('english', body));
|
|
119
|
+
|
|
120
|
+
-- PostGIS
|
|
121
|
+
CREATE INDEX idx_geom ON places USING GIST (geom);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## BRIN (Block Range INdex)
|
|
127
|
+
|
|
128
|
+
Tiny footprint index. Stores min/max per block range. Effective when column values are physically correlated with insertion order (e.g., `created_at` on append-only tables, sensor readings, log IDs).
|
|
129
|
+
|
|
130
|
+
```sql
|
|
131
|
+
CREATE INDEX idx_logs_created ON logs USING BRIN (created_at);
|
|
132
|
+
-- pages_per_range controls granularity (default 128)
|
|
133
|
+
CREATE INDEX idx_metrics_time ON metrics USING BRIN (recorded_at) WITH (pages_per_range = 32);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Do NOT use BRIN if rows are frequently updated or if values are randomly distributed — the index will be ineffective.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Partial indexes
|
|
141
|
+
|
|
142
|
+
Index only a subset of rows. Reduces index size and maintenance cost. The WHERE clause must be satisfied by the query for the index to be used.
|
|
143
|
+
|
|
144
|
+
```sql
|
|
145
|
+
-- Index only active users (most queries filter by status = 'active')
|
|
146
|
+
CREATE INDEX idx_users_active_email ON users (email) WHERE status = 'active';
|
|
147
|
+
|
|
148
|
+
-- Index only unprocessed jobs
|
|
149
|
+
CREATE INDEX idx_jobs_pending ON jobs (created_at) WHERE processed_at IS NULL;
|
|
150
|
+
|
|
151
|
+
-- Unique partial index — email unique only among active users
|
|
152
|
+
CREATE UNIQUE INDEX idx_users_unique_email_active ON users (email) WHERE deleted_at IS NULL;
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The query WHERE clause must be _implied_ by the partial index's condition for the planner to use it. Example: `WHERE status = 'active' AND email = $1` will use `WHERE status = 'active'` partial index.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Expression indexes
|
|
160
|
+
|
|
161
|
+
Index on the result of an expression or function. The query must use the exact same expression.
|
|
162
|
+
|
|
163
|
+
```sql
|
|
164
|
+
-- Case-insensitive unique email
|
|
165
|
+
CREATE UNIQUE INDEX idx_users_email_lower ON users (lower(email));
|
|
166
|
+
-- Query must use: WHERE lower(email) = lower($1)
|
|
167
|
+
|
|
168
|
+
-- Date part
|
|
169
|
+
CREATE INDEX idx_orders_year ON orders (EXTRACT(YEAR FROM created_at));
|
|
170
|
+
|
|
171
|
+
-- Computed column (immutable functions only)
|
|
172
|
+
CREATE INDEX idx_products_slug ON products (regexp_replace(lower(name), '\s+', '-', 'g'));
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Only **immutable** functions can be indexed (same input always yields same output). `NOW()` is volatile — cannot be indexed.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Multicolumn indexes
|
|
180
|
+
|
|
181
|
+
```sql
|
|
182
|
+
CREATE INDEX idx_orders_user_status ON orders (user_id, status, created_at DESC);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Rules:
|
|
186
|
+
|
|
187
|
+
- Columns are used left-to-right. An index on `(a, b, c)` supports queries on `a`, `(a, b)`, and `(a, b, c)`.
|
|
188
|
+
- A query on just `b` or `c` cannot use this index (unless the planner can bitmap-and with other indexes).
|
|
189
|
+
- Leading equality columns are most important; put high-selectivity equality columns first.
|
|
190
|
+
- Put the range/sort column last.
|
|
191
|
+
|
|
192
|
+
Example query that uses `(user_id, status, created_at DESC)`:
|
|
193
|
+
|
|
194
|
+
```sql
|
|
195
|
+
WHERE user_id = $1 AND status = 'pending' ORDER BY created_at DESC LIMIT 10
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Creation options
|
|
201
|
+
|
|
202
|
+
```sql
|
|
203
|
+
-- Non-blocking creation (cannot be in a transaction block)
|
|
204
|
+
CREATE INDEX CONCURRENTLY idx_name ON table (col);
|
|
205
|
+
|
|
206
|
+
-- Named index
|
|
207
|
+
CREATE INDEX idx_orders_customer_id ON orders (customer_id);
|
|
208
|
+
|
|
209
|
+
-- Unique
|
|
210
|
+
CREATE UNIQUE INDEX idx_users_email ON users (email);
|
|
211
|
+
|
|
212
|
+
-- Tablespace
|
|
213
|
+
CREATE INDEX idx_name ON table (col) TABLESPACE fast_ssd;
|
|
214
|
+
|
|
215
|
+
-- Fill factor (leave space for HOT updates)
|
|
216
|
+
CREATE INDEX idx_name ON table (col) WITH (fillfactor = 70);
|
|
217
|
+
|
|
218
|
+
-- Drop
|
|
219
|
+
DROP INDEX idx_name;
|
|
220
|
+
DROP INDEX CONCURRENTLY idx_name; -- non-blocking
|
|
221
|
+
|
|
222
|
+
-- Rebuild (e.g., after bloat)
|
|
223
|
+
REINDEX INDEX idx_name;
|
|
224
|
+
REINDEX TABLE tablename; -- rebuilds all indexes on table
|
|
225
|
+
REINDEX TABLE CONCURRENTLY tablename;
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Inspecting
|
|
231
|
+
|
|
232
|
+
```sql
|
|
233
|
+
-- List indexes on a table
|
|
234
|
+
\di+ tablename -- in psql
|
|
235
|
+
-- or:
|
|
236
|
+
SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'orders';
|
|
237
|
+
|
|
238
|
+
-- Index usage stats (reset on restart)
|
|
239
|
+
SELECT schemaname, tablename, indexname, idx_scan, idx_tup_read, idx_tup_fetch
|
|
240
|
+
FROM pg_stat_user_indexes
|
|
241
|
+
ORDER BY idx_scan;
|
|
242
|
+
|
|
243
|
+
-- Find unused indexes (idx_scan = 0 after some time in prod)
|
|
244
|
+
SELECT indexrelid::regclass, idx_scan
|
|
245
|
+
FROM pg_stat_user_indexes
|
|
246
|
+
WHERE idx_scan = 0 AND schemaname = 'public';
|
|
247
|
+
|
|
248
|
+
-- Index bloat check
|
|
249
|
+
SELECT relname, pg_size_pretty(pg_relation_size(indexrelid)) AS size
|
|
250
|
+
FROM pg_stat_user_indexes
|
|
251
|
+
ORDER BY pg_relation_size(indexrelid) DESC;
|
|
252
|
+
|
|
253
|
+
-- Check if a query uses an index
|
|
254
|
+
EXPLAIN (ANALYZE, BUFFERS) SELECT ...;
|
|
255
|
+
-- Look for: "Index Scan", "Index Only Scan", "Bitmap Index Scan"
|
|
256
|
+
-- "Seq Scan" means no index was used
|
|
257
|
+
```
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# PostgreSQL JSONB Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [json vs jsonb](#json-vs-jsonb)
|
|
6
|
+
2. [Operators](#operators)
|
|
7
|
+
3. [Functions](#functions)
|
|
8
|
+
4. [Modifying JSONB](#modifying)
|
|
9
|
+
5. [GIN indexing](#gin-indexing)
|
|
10
|
+
6. [jsonpath](#jsonpath)
|
|
11
|
+
7. [Common patterns](#patterns)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## json vs jsonb
|
|
16
|
+
|
|
17
|
+
| Feature | `json` | `jsonb` |
|
|
18
|
+
| ---------------- | --------------------------- | ------------------------- |
|
|
19
|
+
| Storage | Raw text (input preserved) | Binary parsed |
|
|
20
|
+
| Write speed | Faster | Slower (parsing overhead) |
|
|
21
|
+
| Read/query speed | Slower (re-parse each time) | **Faster** |
|
|
22
|
+
| Indexing | Not possible | **GIN indexable** |
|
|
23
|
+
| Key order | Preserved | **Not preserved** |
|
|
24
|
+
| Duplicate keys | Last one wins at read | First one kept |
|
|
25
|
+
| Whitespace | Preserved | Stripped |
|
|
26
|
+
|
|
27
|
+
**Always use `jsonb`** unless you need exact input preservation.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Operators
|
|
32
|
+
|
|
33
|
+
### Field access
|
|
34
|
+
|
|
35
|
+
```sql
|
|
36
|
+
data -> 'key' -- returns jsonb (preserves type)
|
|
37
|
+
data ->> 'key' -- returns text
|
|
38
|
+
data -> 2 -- array element by index (0-based)
|
|
39
|
+
data ->> 2 -- array element as text
|
|
40
|
+
data #> '{a,b,c}' -- nested path → jsonb
|
|
41
|
+
data #>> '{a,b,c}' -- nested path → text
|
|
42
|
+
|
|
43
|
+
-- Examples
|
|
44
|
+
SELECT data -> 'name' FROM profiles; -- jsonb: "Alice"
|
|
45
|
+
SELECT data ->> 'name' FROM profiles; -- text: Alice
|
|
46
|
+
SELECT data #>> '{address,city}' FROM profiles; -- text: New York
|
|
47
|
+
SELECT data -> 'tags' -> 0 FROM profiles; -- first tag (jsonb)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Containment
|
|
51
|
+
|
|
52
|
+
```sql
|
|
53
|
+
-- @> left contains right (right is a subset of left)
|
|
54
|
+
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
|
|
55
|
+
SELECT * FROM products WHERE attributes @> '{"tags": ["sale"]}';
|
|
56
|
+
|
|
57
|
+
-- <@ left is contained in right
|
|
58
|
+
SELECT * FROM products WHERE '{"color": "red"}' <@ attributes;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Key existence
|
|
62
|
+
|
|
63
|
+
```sql
|
|
64
|
+
-- ? key exists at top level
|
|
65
|
+
SELECT * FROM profiles WHERE data ? 'email';
|
|
66
|
+
|
|
67
|
+
-- ?| any of these keys exist
|
|
68
|
+
SELECT * FROM profiles WHERE data ?| ARRAY['email', 'phone'];
|
|
69
|
+
|
|
70
|
+
-- ?& all of these keys exist
|
|
71
|
+
SELECT * FROM profiles WHERE data ?& ARRAY['email', 'phone'];
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Concatenation & deletion
|
|
75
|
+
|
|
76
|
+
```sql
|
|
77
|
+
-- || merge two jsonb values (right overwrites left on conflict)
|
|
78
|
+
SELECT '{"a":1}'::jsonb || '{"b":2}'::jsonb; -- {"a":1,"b":2}
|
|
79
|
+
SELECT data || '{"verified":true}'::jsonb FROM users;
|
|
80
|
+
|
|
81
|
+
-- - delete key or array element
|
|
82
|
+
SELECT '{"a":1,"b":2}'::jsonb - 'a'; -- {"b":2}
|
|
83
|
+
SELECT '{"a":1,"b":2}'::jsonb - ARRAY['a','b']; -- {}
|
|
84
|
+
SELECT '[1,2,3]'::jsonb - 1; -- removes index 1 → [1,3]
|
|
85
|
+
|
|
86
|
+
-- #- delete at path
|
|
87
|
+
SELECT '{"a":{"b":1}}'::jsonb #- '{a,b}'; -- {"a":{}}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Functions
|
|
93
|
+
|
|
94
|
+
```sql
|
|
95
|
+
-- Build
|
|
96
|
+
jsonb_build_object('key', value, 'key2', value2)
|
|
97
|
+
jsonb_build_array(1, 2, 3)
|
|
98
|
+
to_jsonb(any_value)
|
|
99
|
+
row_to_json(row)
|
|
100
|
+
|
|
101
|
+
-- Inspect
|
|
102
|
+
jsonb_typeof(data) -- 'object', 'array', 'string', 'number', 'boolean', 'null'
|
|
103
|
+
jsonb_array_length(data) -- length of JSON array
|
|
104
|
+
jsonb_object_keys(data) -- set of top-level keys
|
|
105
|
+
|
|
106
|
+
-- Array elements as rows
|
|
107
|
+
SELECT elem FROM jsonb_array_elements('["a","b","c"]'::jsonb) AS elem;
|
|
108
|
+
SELECT elem ->> 0 FROM jsonb_array_elements_text('["a","b","c"]'::jsonb) AS elem;
|
|
109
|
+
|
|
110
|
+
-- Object as key-value rows
|
|
111
|
+
SELECT key, value FROM jsonb_each('{"a":1,"b":2}'::jsonb);
|
|
112
|
+
SELECT key, value FROM jsonb_each_text('{"a":1,"b":2}'::jsonb);
|
|
113
|
+
|
|
114
|
+
-- Strip nulls
|
|
115
|
+
jsonb_strip_nulls('{"a":1,"b":null}'::jsonb) -- {"a":1}
|
|
116
|
+
|
|
117
|
+
-- Pretty print
|
|
118
|
+
jsonb_pretty('{"a":1}'::jsonb)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Modifying
|
|
124
|
+
|
|
125
|
+
```sql
|
|
126
|
+
-- jsonb_set(target, path, new_value, create_if_missing = true)
|
|
127
|
+
SELECT jsonb_set(data, '{address,city}', '"Rio de Janeiro"') FROM profiles;
|
|
128
|
+
SELECT jsonb_set(data, '{score}', '42', true) FROM profiles; -- creates if missing
|
|
129
|
+
|
|
130
|
+
-- jsonb_insert(target, path, value, insert_after = false)
|
|
131
|
+
-- Inserts into array (doesn't overwrite existing)
|
|
132
|
+
SELECT jsonb_insert('{"a":[1,2,3]}'::jsonb, '{a,1}', '99'); -- [1,99,2,3]
|
|
133
|
+
|
|
134
|
+
-- UPDATE with jsonb_set
|
|
135
|
+
UPDATE users
|
|
136
|
+
SET metadata = jsonb_set(metadata, '{last_login}', to_jsonb(NOW()))
|
|
137
|
+
WHERE id = $1;
|
|
138
|
+
|
|
139
|
+
-- Merge/update top-level key
|
|
140
|
+
UPDATE users
|
|
141
|
+
SET preferences = preferences || '{"theme":"dark"}'::jsonb
|
|
142
|
+
WHERE id = $1;
|
|
143
|
+
|
|
144
|
+
-- Delete a key
|
|
145
|
+
UPDATE users
|
|
146
|
+
SET metadata = metadata - 'temp_token'
|
|
147
|
+
WHERE id = $1;
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## GIN indexing
|
|
153
|
+
|
|
154
|
+
```sql
|
|
155
|
+
-- Default operator class: supports @>, ?, ?|, ?&
|
|
156
|
+
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
|
|
157
|
+
|
|
158
|
+
-- jsonb_path_ops: only supports @>, but smaller and faster
|
|
159
|
+
CREATE INDEX idx_products_attrs_path ON products USING GIN (attributes jsonb_path_ops);
|
|
160
|
+
|
|
161
|
+
-- Index a specific extracted value (B-tree on expression)
|
|
162
|
+
CREATE INDEX idx_users_role ON users ((metadata ->> 'role'));
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
For detailed GIN indexing, see [indexes.md](indexes.md).
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## jsonpath
|
|
170
|
+
|
|
171
|
+
jsonpath is a query language for JSONB (similar to XPath for XML). Available since PG 12.
|
|
172
|
+
|
|
173
|
+
```sql
|
|
174
|
+
-- @ represents the current node
|
|
175
|
+
-- $ represents the root
|
|
176
|
+
|
|
177
|
+
-- Check if path matches
|
|
178
|
+
SELECT jsonb_path_exists('{"a":{"b":1}}', '$.a.b'); -- true
|
|
179
|
+
|
|
180
|
+
-- Extract matching values
|
|
181
|
+
SELECT jsonb_path_query('{"items":[1,2,3]}', '$.items[*]');
|
|
182
|
+
-- Returns 1, 2, 3 as separate rows
|
|
183
|
+
|
|
184
|
+
-- Query first match
|
|
185
|
+
SELECT jsonb_path_query_first('{"users":[{"name":"Alice"},{"name":"Bob"}]}', '$.users[0].name');
|
|
186
|
+
-- "Alice"
|
|
187
|
+
|
|
188
|
+
-- Filter array elements
|
|
189
|
+
SELECT jsonb_path_query('[{"n":1},{"n":5},{"n":2}]'::jsonb, '$[*] ? (@.n > 3)');
|
|
190
|
+
-- {"n":5}
|
|
191
|
+
|
|
192
|
+
-- Arithmetic
|
|
193
|
+
SELECT jsonb_path_query('{"price":100}', '$.price * 1.1');
|
|
194
|
+
-- 110.0
|
|
195
|
+
|
|
196
|
+
-- String operations
|
|
197
|
+
SELECT jsonb_path_query_array('{"tags":["dog","cat","bird"]}', '$.tags[*] ? (@ starts with "d")');
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
jsonpath in WHERE clause:
|
|
201
|
+
|
|
202
|
+
```sql
|
|
203
|
+
SELECT * FROM products WHERE jsonb_path_exists(data, '$.price ? (@ > 100)');
|
|
204
|
+
-- Equivalent to: WHERE (data ->> 'price')::numeric > 100
|
|
205
|
+
-- But jsonpath is more powerful for nested structures
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Common patterns
|
|
211
|
+
|
|
212
|
+
### Validate JSON structure before insert
|
|
213
|
+
|
|
214
|
+
```sql
|
|
215
|
+
ALTER TABLE configs ADD CONSTRAINT valid_schema
|
|
216
|
+
CHECK (jsonb_typeof(settings) = 'object' AND settings ? 'version');
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Aggregate rows into JSON array
|
|
220
|
+
|
|
221
|
+
```sql
|
|
222
|
+
SELECT user_id, jsonb_agg(order_id ORDER BY created_at) AS order_ids
|
|
223
|
+
FROM orders
|
|
224
|
+
GROUP BY user_id;
|
|
225
|
+
|
|
226
|
+
SELECT jsonb_object_agg(key, value) FROM kv_table;
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Expand JSONB array to rows for JOINs
|
|
230
|
+
|
|
231
|
+
```sql
|
|
232
|
+
SELECT p.id, tag
|
|
233
|
+
FROM products p
|
|
234
|
+
CROSS JOIN LATERAL jsonb_array_elements_text(p.tags) AS tag
|
|
235
|
+
WHERE tag = 'sale';
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Store and query flexible attributes
|
|
239
|
+
|
|
240
|
+
```sql
|
|
241
|
+
CREATE TABLE products (
|
|
242
|
+
id bigint PRIMARY KEY,
|
|
243
|
+
name text NOT NULL,
|
|
244
|
+
attributes jsonb NOT NULL DEFAULT '{}'
|
|
245
|
+
);
|
|
246
|
+
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
|
|
247
|
+
|
|
248
|
+
-- Query by any attribute:
|
|
249
|
+
SELECT * FROM products WHERE attributes @> '{"color":"red","size":"M"}';
|
|
250
|
+
-- Add new attribute without schema change:
|
|
251
|
+
UPDATE products SET attributes = attributes || '{"material":"cotton"}' WHERE id = 1;
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Upsert into JSONB key
|
|
255
|
+
|
|
256
|
+
```sql
|
|
257
|
+
-- Atomic "set if not exists"
|
|
258
|
+
UPDATE users
|
|
259
|
+
SET metadata = jsonb_set(metadata, '{onboarding_completed}', 'true', true)
|
|
260
|
+
WHERE id = $1 AND NOT (metadata ? 'onboarding_completed');
|
|
261
|
+
```
|