chub-dev 0.1.0 → 0.1.2-beta.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/README.md +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1500 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: distributed-db
|
|
3
|
+
description: "CockroachDB with node-postgres (pg) - JavaScript guide for connecting to CockroachDB from Node.js applications"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "javascript"
|
|
6
|
+
versions: "8.16.3"
|
|
7
|
+
updated-on: "2026-03-02"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "cockroachdb,distributed-db,sql,postgres,database"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# CockroachDB with node-postgres (pg) - JavaScript Guide
|
|
13
|
+
|
|
14
|
+
## Golden Rule
|
|
15
|
+
|
|
16
|
+
**ALWAYS use the `pg` (node-postgres) package version 8.16.3 or later** to connect to CockroachDB from Node.js applications.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install pg
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
CockroachDB is PostgreSQL wire-compatible, meaning it uses the PostgreSQL protocol. The official recommendation is to use the standard PostgreSQL `pg` driver (node-postgres) for JavaScript/Node.js applications.
|
|
23
|
+
|
|
24
|
+
**DO NOT use:**
|
|
25
|
+
- Unofficial or deprecated CockroachDB-specific packages
|
|
26
|
+
- Old versions of `pg` that may not support modern features
|
|
27
|
+
- Random third-party wrappers without proper maintenance
|
|
28
|
+
|
|
29
|
+
**ALWAYS use `pg` (node-postgres)** - it is the officially supported and recommended driver.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### Basic Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install pg
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### With TypeScript Support
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install pg @types/pg
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Additional Dependencies (Optional)
|
|
48
|
+
|
|
49
|
+
For SSL connections with custom certificates:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install pg fs
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Environment Variables
|
|
58
|
+
|
|
59
|
+
### Basic Configuration
|
|
60
|
+
|
|
61
|
+
Create a `.env` file:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Database connection
|
|
65
|
+
DATABASE_URL=postgresql://root@localhost:26257/defaultdb?sslmode=disable
|
|
66
|
+
|
|
67
|
+
# Or separate variables
|
|
68
|
+
DB_USER=root
|
|
69
|
+
DB_HOST=localhost
|
|
70
|
+
DB_PORT=26257
|
|
71
|
+
DB_NAME=defaultdb
|
|
72
|
+
DB_PASSWORD=
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Secure Production Configuration
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# CockroachDB Serverless/Cloud
|
|
79
|
+
DATABASE_URL=postgresql://username:password@host.cockroachlabs.cloud:26257/database?sslmode=verify-full
|
|
80
|
+
|
|
81
|
+
# With certificate paths
|
|
82
|
+
DB_USER=maxroach
|
|
83
|
+
DB_HOST=blue-dog-147.6wr.cockroachlabs.cloud
|
|
84
|
+
DB_PORT=26257
|
|
85
|
+
DB_NAME=defaultdb
|
|
86
|
+
DB_PASSWORD=YourSecurePassword
|
|
87
|
+
DB_SSL_MODE=verify-full
|
|
88
|
+
DB_SSL_ROOT_CERT=/path/to/root.crt
|
|
89
|
+
DB_SSL_CERT=/path/to/client.crt
|
|
90
|
+
DB_SSL_KEY=/path/to/client.key
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Loading Environment Variables
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
require('dotenv').config();
|
|
97
|
+
|
|
98
|
+
const config = {
|
|
99
|
+
user: process.env.DB_USER || 'root',
|
|
100
|
+
host: process.env.DB_HOST || 'localhost',
|
|
101
|
+
database: process.env.DB_NAME || 'defaultdb',
|
|
102
|
+
password: process.env.DB_PASSWORD || '',
|
|
103
|
+
port: parseInt(process.env.DB_PORT) || 26257,
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Initialization
|
|
110
|
+
|
|
111
|
+
### Basic Client Connection
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
const { Client } = require('pg');
|
|
115
|
+
|
|
116
|
+
const client = new Client({
|
|
117
|
+
user: 'root',
|
|
118
|
+
host: 'localhost',
|
|
119
|
+
database: 'defaultdb',
|
|
120
|
+
port: 26257,
|
|
121
|
+
ssl: false
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
client.connect()
|
|
125
|
+
.then(() => console.log('Connected to CockroachDB'))
|
|
126
|
+
.catch(err => console.error('Connection error', err.stack));
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Connection Pool (Recommended)
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
const { Pool } = require('pg');
|
|
133
|
+
|
|
134
|
+
const pool = new Pool({
|
|
135
|
+
user: 'root',
|
|
136
|
+
host: 'localhost',
|
|
137
|
+
database: 'defaultdb',
|
|
138
|
+
port: 26257,
|
|
139
|
+
max: 10, // Maximum pool size
|
|
140
|
+
min: 2, // Minimum pool size
|
|
141
|
+
idleTimeoutMillis: 30000, // Close idle clients after 30 seconds
|
|
142
|
+
connectionTimeoutMillis: 2000, // Return error after 2 seconds if connection unavailable
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Test the connection
|
|
146
|
+
pool.query('SELECT NOW()', (err, res) => {
|
|
147
|
+
if (err) {
|
|
148
|
+
console.error('Error executing query', err.stack);
|
|
149
|
+
} else {
|
|
150
|
+
console.log('Connected to CockroachDB:', res.rows[0]);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Async/Await Pool Connection
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
const { Pool } = require('pg');
|
|
159
|
+
|
|
160
|
+
const pool = new Pool({
|
|
161
|
+
user: 'root',
|
|
162
|
+
host: 'localhost',
|
|
163
|
+
database: 'defaultdb',
|
|
164
|
+
port: 26257,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
async function testConnection() {
|
|
168
|
+
try {
|
|
169
|
+
const client = await pool.connect();
|
|
170
|
+
const res = await client.query('SELECT version()');
|
|
171
|
+
console.log('CockroachDB version:', res.rows[0].version);
|
|
172
|
+
client.release();
|
|
173
|
+
} catch (err) {
|
|
174
|
+
console.error('Error:', err);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
testConnection();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### SSL Configuration for Production
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
const { Pool } = require('pg');
|
|
185
|
+
const fs = require('fs');
|
|
186
|
+
|
|
187
|
+
const pool = new Pool({
|
|
188
|
+
user: 'maxroach',
|
|
189
|
+
host: 'blue-dog-147.cockroachlabs.cloud',
|
|
190
|
+
database: 'defaultdb',
|
|
191
|
+
password: 'YourPassword',
|
|
192
|
+
port: 26257,
|
|
193
|
+
ssl: {
|
|
194
|
+
rejectUnauthorized: true,
|
|
195
|
+
ca: fs.readFileSync('/path/to/root.crt').toString(),
|
|
196
|
+
cert: fs.readFileSync('/path/to/client.crt').toString(),
|
|
197
|
+
key: fs.readFileSync('/path/to/client.key').toString(),
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Connection String Format
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
const { Pool } = require('pg');
|
|
206
|
+
|
|
207
|
+
// Local insecure cluster
|
|
208
|
+
const pool = new Pool({
|
|
209
|
+
connectionString: 'postgresql://root@localhost:26257/defaultdb?sslmode=disable'
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Production secure cluster
|
|
213
|
+
const pool = new Pool({
|
|
214
|
+
connectionString: 'postgresql://user:password@host.cockroachlabs.cloud:26257/database?sslmode=verify-full'
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// With environment variable
|
|
218
|
+
const pool = new Pool({
|
|
219
|
+
connectionString: process.env.DATABASE_URL
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Complete Initialization with Error Handling
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
const { Pool } = require('pg');
|
|
227
|
+
|
|
228
|
+
const pool = new Pool({
|
|
229
|
+
user: process.env.DB_USER || 'root',
|
|
230
|
+
host: process.env.DB_HOST || 'localhost',
|
|
231
|
+
database: process.env.DB_NAME || 'defaultdb',
|
|
232
|
+
password: process.env.DB_PASSWORD || '',
|
|
233
|
+
port: parseInt(process.env.DB_PORT) || 26257,
|
|
234
|
+
max: 20,
|
|
235
|
+
min: 5,
|
|
236
|
+
idleTimeoutMillis: 300000, // 5 minutes
|
|
237
|
+
connectionTimeoutMillis: 5000,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
pool.on('error', (err, client) => {
|
|
241
|
+
console.error('Unexpected error on idle client', err);
|
|
242
|
+
process.exit(-1);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
pool.on('connect', () => {
|
|
246
|
+
console.log('New client connected to CockroachDB pool');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
pool.on('remove', () => {
|
|
250
|
+
console.log('Client removed from pool');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
async function initializeDatabase() {
|
|
254
|
+
const client = await pool.connect();
|
|
255
|
+
try {
|
|
256
|
+
await client.query('SELECT 1');
|
|
257
|
+
console.log('Database connection established');
|
|
258
|
+
} catch (err) {
|
|
259
|
+
console.error('Failed to connect to database:', err);
|
|
260
|
+
throw err;
|
|
261
|
+
} finally {
|
|
262
|
+
client.release();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
initializeDatabase();
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Core API Operations
|
|
272
|
+
|
|
273
|
+
### Basic Queries
|
|
274
|
+
|
|
275
|
+
#### Simple SELECT Query
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
const { Pool } = require('pg');
|
|
279
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
280
|
+
|
|
281
|
+
async function getUsers() {
|
|
282
|
+
const client = await pool.connect();
|
|
283
|
+
try {
|
|
284
|
+
const result = await client.query('SELECT * FROM users');
|
|
285
|
+
console.log('Users:', result.rows);
|
|
286
|
+
return result.rows;
|
|
287
|
+
} catch (err) {
|
|
288
|
+
console.error('Query error:', err);
|
|
289
|
+
throw err;
|
|
290
|
+
} finally {
|
|
291
|
+
client.release();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
getUsers();
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### Parameterized Query
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
async function getUserById(id) {
|
|
302
|
+
const client = await pool.connect();
|
|
303
|
+
try {
|
|
304
|
+
const query = 'SELECT * FROM users WHERE id = $1';
|
|
305
|
+
const result = await client.query(query, [id]);
|
|
306
|
+
return result.rows[0];
|
|
307
|
+
} catch (err) {
|
|
308
|
+
console.error('Error fetching user:', err);
|
|
309
|
+
throw err;
|
|
310
|
+
} finally {
|
|
311
|
+
client.release();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
getUserById('123e4567-e89b-12d3-a456-426614174000');
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### Multiple Parameters
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
async function searchUsers(city, minAge) {
|
|
322
|
+
const client = await pool.connect();
|
|
323
|
+
try {
|
|
324
|
+
const query = 'SELECT * FROM users WHERE city = $1 AND age >= $2';
|
|
325
|
+
const result = await client.query(query, [city, minAge]);
|
|
326
|
+
return result.rows;
|
|
327
|
+
} finally {
|
|
328
|
+
client.release();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
searchUsers('Seattle', 25);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### INSERT Operations
|
|
336
|
+
|
|
337
|
+
#### Single Row Insert
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
340
|
+
async function createUser(name, email, city) {
|
|
341
|
+
const client = await pool.connect();
|
|
342
|
+
try {
|
|
343
|
+
const query = `
|
|
344
|
+
INSERT INTO users (name, email, city)
|
|
345
|
+
VALUES ($1, $2, $3)
|
|
346
|
+
RETURNING *
|
|
347
|
+
`;
|
|
348
|
+
const result = await client.query(query, [name, email, city]);
|
|
349
|
+
console.log('Created user:', result.rows[0]);
|
|
350
|
+
return result.rows[0];
|
|
351
|
+
} finally {
|
|
352
|
+
client.release();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
createUser('John Doe', 'john@example.com', 'New York');
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### Multiple Row Insert
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
async function createMultipleUsers(users) {
|
|
363
|
+
const client = await pool.connect();
|
|
364
|
+
try {
|
|
365
|
+
const query = `
|
|
366
|
+
INSERT INTO users (name, email, city)
|
|
367
|
+
VALUES
|
|
368
|
+
($1, $2, $3),
|
|
369
|
+
($4, $5, $6),
|
|
370
|
+
($7, $8, $9)
|
|
371
|
+
RETURNING id, name
|
|
372
|
+
`;
|
|
373
|
+
const values = users.flatMap(u => [u.name, u.email, u.city]);
|
|
374
|
+
const result = await client.query(query, values);
|
|
375
|
+
return result.rows;
|
|
376
|
+
} finally {
|
|
377
|
+
client.release();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
createMultipleUsers([
|
|
382
|
+
{ name: 'Alice', email: 'alice@example.com', city: 'Boston' },
|
|
383
|
+
{ name: 'Bob', email: 'bob@example.com', city: 'Chicago' },
|
|
384
|
+
{ name: 'Carol', email: 'carol@example.com', city: 'Denver' }
|
|
385
|
+
]);
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
#### Insert with Default Values
|
|
389
|
+
|
|
390
|
+
```javascript
|
|
391
|
+
async function createDriver(city) {
|
|
392
|
+
const client = await pool.connect();
|
|
393
|
+
try {
|
|
394
|
+
const query = `
|
|
395
|
+
INSERT INTO drivers (id, city, created_at)
|
|
396
|
+
VALUES (gen_random_uuid(), $1, now())
|
|
397
|
+
RETURNING *
|
|
398
|
+
`;
|
|
399
|
+
const result = await client.query(query, [city]);
|
|
400
|
+
return result.rows[0];
|
|
401
|
+
} finally {
|
|
402
|
+
client.release();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
createDriver('Seattle');
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
#### Insert with ON CONFLICT
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
async function upsertUser(email, name, city) {
|
|
413
|
+
const client = await pool.connect();
|
|
414
|
+
try {
|
|
415
|
+
const query = `
|
|
416
|
+
INSERT INTO users (email, name, city)
|
|
417
|
+
VALUES ($1, $2, $3)
|
|
418
|
+
ON CONFLICT (email)
|
|
419
|
+
DO UPDATE SET
|
|
420
|
+
name = EXCLUDED.name,
|
|
421
|
+
city = EXCLUDED.city,
|
|
422
|
+
updated_at = now()
|
|
423
|
+
RETURNING *
|
|
424
|
+
`;
|
|
425
|
+
const result = await client.query(query, [email, name, city]);
|
|
426
|
+
return result.rows[0];
|
|
427
|
+
} finally {
|
|
428
|
+
client.release();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
upsertUser('john@example.com', 'John Smith', 'Los Angeles');
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### UPDATE Operations
|
|
436
|
+
|
|
437
|
+
#### Simple Update
|
|
438
|
+
|
|
439
|
+
```javascript
|
|
440
|
+
async function updateUserCity(userId, newCity) {
|
|
441
|
+
const client = await pool.connect();
|
|
442
|
+
try {
|
|
443
|
+
const query = `
|
|
444
|
+
UPDATE users
|
|
445
|
+
SET city = $1, updated_at = now()
|
|
446
|
+
WHERE id = $2
|
|
447
|
+
RETURNING *
|
|
448
|
+
`;
|
|
449
|
+
const result = await client.query(query, [newCity, userId]);
|
|
450
|
+
return result.rows[0];
|
|
451
|
+
} finally {
|
|
452
|
+
client.release();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
updateUserCity('123e4567-e89b-12d3-a456-426614174000', 'Portland');
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
#### Conditional Update
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
async function activateUser(email) {
|
|
463
|
+
const client = await pool.connect();
|
|
464
|
+
try {
|
|
465
|
+
const query = `
|
|
466
|
+
UPDATE users
|
|
467
|
+
SET status = 'active', activated_at = now()
|
|
468
|
+
WHERE email = $1 AND status = 'pending'
|
|
469
|
+
RETURNING id, email, status
|
|
470
|
+
`;
|
|
471
|
+
const result = await client.query(query, [email]);
|
|
472
|
+
if (result.rowCount === 0) {
|
|
473
|
+
throw new Error('User not found or already active');
|
|
474
|
+
}
|
|
475
|
+
return result.rows[0];
|
|
476
|
+
} finally {
|
|
477
|
+
client.release();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
activateUser('john@example.com');
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
#### Bulk Update
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
async function updateVehicleStatus(city, newStatus) {
|
|
488
|
+
const client = await pool.connect();
|
|
489
|
+
try {
|
|
490
|
+
const query = `
|
|
491
|
+
UPDATE vehicles
|
|
492
|
+
SET status = $1
|
|
493
|
+
WHERE city = $2
|
|
494
|
+
RETURNING id, status
|
|
495
|
+
`;
|
|
496
|
+
const result = await client.query(query, [newStatus, city]);
|
|
497
|
+
console.log(`Updated ${result.rowCount} vehicles`);
|
|
498
|
+
return result.rows;
|
|
499
|
+
} finally {
|
|
500
|
+
client.release();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
updateVehicleStatus('New York', 'available');
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### DELETE Operations
|
|
508
|
+
|
|
509
|
+
#### Simple Delete
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
async function deleteUser(userId) {
|
|
513
|
+
const client = await pool.connect();
|
|
514
|
+
try {
|
|
515
|
+
const query = 'DELETE FROM users WHERE id = $1 RETURNING *';
|
|
516
|
+
const result = await client.query(query, [userId]);
|
|
517
|
+
return result.rows[0];
|
|
518
|
+
} finally {
|
|
519
|
+
client.release();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
deleteUser('123e4567-e89b-12d3-a456-426614174000');
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### Conditional Delete
|
|
527
|
+
|
|
528
|
+
```javascript
|
|
529
|
+
async function deleteInactiveUsers(daysInactive) {
|
|
530
|
+
const client = await pool.connect();
|
|
531
|
+
try {
|
|
532
|
+
const result = await client.query(
|
|
533
|
+
`DELETE FROM users
|
|
534
|
+
WHERE last_login < now() - make_interval(days => $1)
|
|
535
|
+
RETURNING id, email`,
|
|
536
|
+
[daysInactive]
|
|
537
|
+
);
|
|
538
|
+
console.log(`Deleted ${result.rowCount} inactive users`);
|
|
539
|
+
return result.rows;
|
|
540
|
+
} finally {
|
|
541
|
+
client.release();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
deleteInactiveUsers(90);
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### Delete with JOIN
|
|
549
|
+
|
|
550
|
+
```javascript
|
|
551
|
+
async function deleteUserOrders(userId) {
|
|
552
|
+
const client = await pool.connect();
|
|
553
|
+
try {
|
|
554
|
+
const query = `
|
|
555
|
+
DELETE FROM orders
|
|
556
|
+
WHERE user_id = $1
|
|
557
|
+
RETURNING id, total_amount
|
|
558
|
+
`;
|
|
559
|
+
const result = await client.query(query, [userId]);
|
|
560
|
+
return result.rows;
|
|
561
|
+
} finally {
|
|
562
|
+
client.release();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
deleteUserOrders('123e4567-e89b-12d3-a456-426614174000');
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## Transactions
|
|
572
|
+
|
|
573
|
+
### Basic Transaction
|
|
574
|
+
|
|
575
|
+
```javascript
|
|
576
|
+
async function transferFunds(fromAccount, toAccount, amount) {
|
|
577
|
+
const client = await pool.connect();
|
|
578
|
+
try {
|
|
579
|
+
await client.query('BEGIN');
|
|
580
|
+
|
|
581
|
+
// Deduct from sender
|
|
582
|
+
await client.query(
|
|
583
|
+
'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
|
|
584
|
+
[amount, fromAccount]
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
// Add to receiver
|
|
588
|
+
await client.query(
|
|
589
|
+
'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
|
|
590
|
+
[amount, toAccount]
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
await client.query('COMMIT');
|
|
594
|
+
console.log('Transfer completed successfully');
|
|
595
|
+
} catch (err) {
|
|
596
|
+
await client.query('ROLLBACK');
|
|
597
|
+
console.error('Transfer failed, rolled back:', err);
|
|
598
|
+
throw err;
|
|
599
|
+
} finally {
|
|
600
|
+
client.release();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
transferFunds('account-1', 'account-2', 100.50);
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Transaction with Savepoints
|
|
608
|
+
|
|
609
|
+
```javascript
|
|
610
|
+
async function complexTransaction() {
|
|
611
|
+
const client = await pool.connect();
|
|
612
|
+
try {
|
|
613
|
+
await client.query('BEGIN');
|
|
614
|
+
|
|
615
|
+
// First operation
|
|
616
|
+
await client.query('INSERT INTO logs (message) VALUES ($1)', ['Started']);
|
|
617
|
+
|
|
618
|
+
// Create savepoint
|
|
619
|
+
await client.query('SAVEPOINT sp1');
|
|
620
|
+
|
|
621
|
+
try {
|
|
622
|
+
await client.query('INSERT INTO users (email) VALUES ($1)', ['test@example.com']);
|
|
623
|
+
} catch (err) {
|
|
624
|
+
// Rollback to savepoint on error
|
|
625
|
+
await client.query('ROLLBACK TO SAVEPOINT sp1');
|
|
626
|
+
console.log('User insert failed, rolled back to savepoint');
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Continue with transaction
|
|
630
|
+
await client.query('INSERT INTO logs (message) VALUES ($1)', ['Completed']);
|
|
631
|
+
|
|
632
|
+
await client.query('COMMIT');
|
|
633
|
+
} catch (err) {
|
|
634
|
+
await client.query('ROLLBACK');
|
|
635
|
+
throw err;
|
|
636
|
+
} finally {
|
|
637
|
+
client.release();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
complexTransaction();
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### Transaction with Retry Logic (CockroachDB Specific)
|
|
645
|
+
|
|
646
|
+
```javascript
|
|
647
|
+
async function transferWithRetry(fromAccount, toAccount, amount, maxRetries = 3) {
|
|
648
|
+
let retries = 0;
|
|
649
|
+
|
|
650
|
+
while (retries < maxRetries) {
|
|
651
|
+
const client = await pool.connect();
|
|
652
|
+
try {
|
|
653
|
+
await client.query('BEGIN');
|
|
654
|
+
|
|
655
|
+
const fromResult = await client.query(
|
|
656
|
+
'UPDATE accounts SET balance = balance - $1 WHERE id = $2 RETURNING balance',
|
|
657
|
+
[amount, fromAccount]
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
if (fromResult.rows[0].balance < 0) {
|
|
661
|
+
throw new Error('Insufficient funds');
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
await client.query(
|
|
665
|
+
'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
|
|
666
|
+
[amount, toAccount]
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
await client.query('COMMIT');
|
|
670
|
+
client.release();
|
|
671
|
+
return { success: true, retries };
|
|
672
|
+
|
|
673
|
+
} catch (err) {
|
|
674
|
+
await client.query('ROLLBACK');
|
|
675
|
+
client.release();
|
|
676
|
+
|
|
677
|
+
// Check if it's a serialization error (40001)
|
|
678
|
+
if (err.code === '40001' && retries < maxRetries - 1) {
|
|
679
|
+
retries++;
|
|
680
|
+
console.log(`Retrying transaction (attempt ${retries + 1})`);
|
|
681
|
+
await new Promise(resolve => setTimeout(resolve, Math.random() * 100));
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
throw err;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
throw new Error('Transaction failed after maximum retries');
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
transferWithRetry('account-1', 'account-2', 100.50);
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### Read-Only Transaction
|
|
696
|
+
|
|
697
|
+
```javascript
|
|
698
|
+
async function getAccountSummary(userId) {
|
|
699
|
+
const client = await pool.connect();
|
|
700
|
+
try {
|
|
701
|
+
await client.query('BEGIN TRANSACTION READ ONLY');
|
|
702
|
+
|
|
703
|
+
const userResult = await client.query(
|
|
704
|
+
'SELECT * FROM users WHERE id = $1',
|
|
705
|
+
[userId]
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
const accountsResult = await client.query(
|
|
709
|
+
'SELECT * FROM accounts WHERE user_id = $1',
|
|
710
|
+
[userId]
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
const transactionsResult = await client.query(
|
|
714
|
+
'SELECT * FROM transactions WHERE user_id = $1 ORDER BY created_at DESC LIMIT 10',
|
|
715
|
+
[userId]
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
await client.query('COMMIT');
|
|
719
|
+
|
|
720
|
+
return {
|
|
721
|
+
user: userResult.rows[0],
|
|
722
|
+
accounts: accountsResult.rows,
|
|
723
|
+
recentTransactions: transactionsResult.rows
|
|
724
|
+
};
|
|
725
|
+
} catch (err) {
|
|
726
|
+
await client.query('ROLLBACK');
|
|
727
|
+
throw err;
|
|
728
|
+
} finally {
|
|
729
|
+
client.release();
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
getAccountSummary('user-123');
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
---
|
|
737
|
+
|
|
738
|
+
## Advanced Features
|
|
739
|
+
|
|
740
|
+
### JSON/JSONB Operations
|
|
741
|
+
|
|
742
|
+
#### Inserting JSON Data
|
|
743
|
+
|
|
744
|
+
```javascript
|
|
745
|
+
async function createProduct(name, metadata) {
|
|
746
|
+
const client = await pool.connect();
|
|
747
|
+
try {
|
|
748
|
+
const query = `
|
|
749
|
+
INSERT INTO products (name, metadata)
|
|
750
|
+
VALUES ($1, $2::jsonb)
|
|
751
|
+
RETURNING *
|
|
752
|
+
`;
|
|
753
|
+
const result = await client.query(query, [name, JSON.stringify(metadata)]);
|
|
754
|
+
return result.rows[0];
|
|
755
|
+
} finally {
|
|
756
|
+
client.release();
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
createProduct('Laptop', {
|
|
761
|
+
brand: 'Dell',
|
|
762
|
+
specs: { ram: '16GB', storage: '512GB SSD' },
|
|
763
|
+
tags: ['electronics', 'computers']
|
|
764
|
+
});
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
#### Querying JSON Fields
|
|
768
|
+
|
|
769
|
+
```javascript
|
|
770
|
+
async function findProductsByBrand(brand) {
|
|
771
|
+
const client = await pool.connect();
|
|
772
|
+
try {
|
|
773
|
+
const query = `
|
|
774
|
+
SELECT * FROM products
|
|
775
|
+
WHERE metadata->>'brand' = $1
|
|
776
|
+
`;
|
|
777
|
+
const result = await client.query(query, [brand]);
|
|
778
|
+
return result.rows;
|
|
779
|
+
} finally {
|
|
780
|
+
client.release();
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
findProductsByBrand('Dell');
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
#### Nested JSON Queries
|
|
788
|
+
|
|
789
|
+
```javascript
|
|
790
|
+
async function findProductsByRAM(ram) {
|
|
791
|
+
const client = await pool.connect();
|
|
792
|
+
try {
|
|
793
|
+
const query = `
|
|
794
|
+
SELECT * FROM products
|
|
795
|
+
WHERE metadata->'specs'->>'ram' = $1
|
|
796
|
+
`;
|
|
797
|
+
const result = await client.query(query, [ram]);
|
|
798
|
+
return result.rows;
|
|
799
|
+
} finally {
|
|
800
|
+
client.release();
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
findProductsByRAM('16GB');
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
#### Updating JSON Fields
|
|
808
|
+
|
|
809
|
+
```javascript
|
|
810
|
+
async function updateProductPrice(productId, newPrice) {
|
|
811
|
+
const client = await pool.connect();
|
|
812
|
+
try {
|
|
813
|
+
const query = `
|
|
814
|
+
UPDATE products
|
|
815
|
+
SET metadata = jsonb_set(metadata, '{price}', $1::jsonb)
|
|
816
|
+
WHERE id = $2
|
|
817
|
+
RETURNING *
|
|
818
|
+
`;
|
|
819
|
+
const result = await client.query(query, [JSON.stringify(newPrice), productId]);
|
|
820
|
+
return result.rows[0];
|
|
821
|
+
} finally {
|
|
822
|
+
client.release();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
updateProductPrice('product-123', 999.99);
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
#### JSON Array Operations
|
|
830
|
+
|
|
831
|
+
```javascript
|
|
832
|
+
async function addProductTag(productId, tag) {
|
|
833
|
+
const client = await pool.connect();
|
|
834
|
+
try {
|
|
835
|
+
const query = `
|
|
836
|
+
UPDATE products
|
|
837
|
+
SET metadata = jsonb_set(
|
|
838
|
+
metadata,
|
|
839
|
+
'{tags}',
|
|
840
|
+
(metadata->'tags')::jsonb || $1::jsonb
|
|
841
|
+
)
|
|
842
|
+
WHERE id = $2
|
|
843
|
+
RETURNING *
|
|
844
|
+
`;
|
|
845
|
+
const result = await client.query(query, [JSON.stringify([tag]), productId]);
|
|
846
|
+
return result.rows[0];
|
|
847
|
+
} finally {
|
|
848
|
+
client.release();
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
addProductTag('product-123', 'featured');
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### Array Operations
|
|
856
|
+
|
|
857
|
+
#### Working with Arrays
|
|
858
|
+
|
|
859
|
+
```javascript
|
|
860
|
+
async function createUserWithTags(email, tags) {
|
|
861
|
+
const client = await pool.connect();
|
|
862
|
+
try {
|
|
863
|
+
const query = `
|
|
864
|
+
INSERT INTO users (email, tags)
|
|
865
|
+
VALUES ($1, $2)
|
|
866
|
+
RETURNING *
|
|
867
|
+
`;
|
|
868
|
+
const result = await client.query(query, [email, tags]);
|
|
869
|
+
return result.rows[0];
|
|
870
|
+
} finally {
|
|
871
|
+
client.release();
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
createUserWithTags('john@example.com', ['premium', 'verified']);
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
#### Querying Arrays
|
|
879
|
+
|
|
880
|
+
```javascript
|
|
881
|
+
async function findUsersByTag(tag) {
|
|
882
|
+
const client = await pool.connect();
|
|
883
|
+
try {
|
|
884
|
+
const query = `
|
|
885
|
+
SELECT * FROM users
|
|
886
|
+
WHERE $1 = ANY(tags)
|
|
887
|
+
`;
|
|
888
|
+
const result = await client.query(query, [tag]);
|
|
889
|
+
return result.rows;
|
|
890
|
+
} finally {
|
|
891
|
+
client.release();
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
findUsersByTag('premium');
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
#### Array Contains Query
|
|
899
|
+
|
|
900
|
+
```javascript
|
|
901
|
+
async function findUsersWithAllTags(requiredTags) {
|
|
902
|
+
const client = await pool.connect();
|
|
903
|
+
try {
|
|
904
|
+
const query = `
|
|
905
|
+
SELECT * FROM users
|
|
906
|
+
WHERE tags @> $1::text[]
|
|
907
|
+
`;
|
|
908
|
+
const result = await client.query(query, [requiredTags]);
|
|
909
|
+
return result.rows;
|
|
910
|
+
} finally {
|
|
911
|
+
client.release();
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
findUsersWithAllTags(['premium', 'verified']);
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
### Full-Text Search
|
|
919
|
+
|
|
920
|
+
#### Creating a Text Search Query
|
|
921
|
+
|
|
922
|
+
```javascript
|
|
923
|
+
async function searchArticles(searchTerm) {
|
|
924
|
+
const client = await pool.connect();
|
|
925
|
+
try {
|
|
926
|
+
const query = `
|
|
927
|
+
SELECT id, title, content,
|
|
928
|
+
ts_rank(to_tsvector('english', title || ' ' || content), query) AS rank
|
|
929
|
+
FROM articles,
|
|
930
|
+
to_tsquery('english', $1) query
|
|
931
|
+
WHERE to_tsvector('english', title || ' ' || content) @@ query
|
|
932
|
+
ORDER BY rank DESC
|
|
933
|
+
LIMIT 20
|
|
934
|
+
`;
|
|
935
|
+
const result = await client.query(query, [searchTerm]);
|
|
936
|
+
return result.rows;
|
|
937
|
+
} finally {
|
|
938
|
+
client.release();
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
searchArticles('database performance');
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
### Aggregations and Analytics
|
|
946
|
+
|
|
947
|
+
#### Group By and Aggregation
|
|
948
|
+
|
|
949
|
+
```javascript
|
|
950
|
+
async function getUserStatsByCity() {
|
|
951
|
+
const client = await pool.connect();
|
|
952
|
+
try {
|
|
953
|
+
const query = `
|
|
954
|
+
SELECT
|
|
955
|
+
city,
|
|
956
|
+
COUNT(*) as user_count,
|
|
957
|
+
AVG(age) as avg_age,
|
|
958
|
+
MIN(created_at) as first_user,
|
|
959
|
+
MAX(created_at) as latest_user
|
|
960
|
+
FROM users
|
|
961
|
+
GROUP BY city
|
|
962
|
+
ORDER BY user_count DESC
|
|
963
|
+
`;
|
|
964
|
+
const result = await client.query(query);
|
|
965
|
+
return result.rows;
|
|
966
|
+
} finally {
|
|
967
|
+
client.release();
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
getUserStatsByCity();
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
#### Window Functions
|
|
975
|
+
|
|
976
|
+
```javascript
|
|
977
|
+
async function getRankedProducts() {
|
|
978
|
+
const client = await pool.connect();
|
|
979
|
+
try {
|
|
980
|
+
const query = `
|
|
981
|
+
SELECT
|
|
982
|
+
name,
|
|
983
|
+
category,
|
|
984
|
+
price,
|
|
985
|
+
RANK() OVER (PARTITION BY category ORDER BY price DESC) as price_rank,
|
|
986
|
+
AVG(price) OVER (PARTITION BY category) as category_avg_price
|
|
987
|
+
FROM products
|
|
988
|
+
`;
|
|
989
|
+
const result = await client.query(query);
|
|
990
|
+
return result.rows;
|
|
991
|
+
} finally {
|
|
992
|
+
client.release();
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
getRankedProducts();
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
### Common Table Expressions (CTEs)
|
|
1000
|
+
|
|
1001
|
+
```javascript
|
|
1002
|
+
async function getTopSpenders(limit = 10) {
|
|
1003
|
+
const client = await pool.connect();
|
|
1004
|
+
try {
|
|
1005
|
+
const query = `
|
|
1006
|
+
WITH user_totals AS (
|
|
1007
|
+
SELECT
|
|
1008
|
+
user_id,
|
|
1009
|
+
SUM(amount) as total_spent,
|
|
1010
|
+
COUNT(*) as order_count
|
|
1011
|
+
FROM orders
|
|
1012
|
+
WHERE status = 'completed'
|
|
1013
|
+
GROUP BY user_id
|
|
1014
|
+
)
|
|
1015
|
+
SELECT
|
|
1016
|
+
u.id,
|
|
1017
|
+
u.email,
|
|
1018
|
+
ut.total_spent,
|
|
1019
|
+
ut.order_count
|
|
1020
|
+
FROM users u
|
|
1021
|
+
JOIN user_totals ut ON u.id = ut.user_id
|
|
1022
|
+
ORDER BY ut.total_spent DESC
|
|
1023
|
+
LIMIT $1
|
|
1024
|
+
`;
|
|
1025
|
+
const result = await client.query(query, [limit]);
|
|
1026
|
+
return result.rows;
|
|
1027
|
+
} finally {
|
|
1028
|
+
client.release();
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
getTopSpenders(20);
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
### Batch Operations
|
|
1036
|
+
|
|
1037
|
+
#### Batch Insert
|
|
1038
|
+
|
|
1039
|
+
```javascript
|
|
1040
|
+
async function batchInsertUsers(users) {
|
|
1041
|
+
const client = await pool.connect();
|
|
1042
|
+
try {
|
|
1043
|
+
await client.query('BEGIN');
|
|
1044
|
+
|
|
1045
|
+
for (const user of users) {
|
|
1046
|
+
await client.query(
|
|
1047
|
+
'INSERT INTO users (name, email, city) VALUES ($1, $2, $3)',
|
|
1048
|
+
[user.name, user.email, user.city]
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
await client.query('COMMIT');
|
|
1053
|
+
console.log(`Inserted ${users.length} users`);
|
|
1054
|
+
} catch (err) {
|
|
1055
|
+
await client.query('ROLLBACK');
|
|
1056
|
+
throw err;
|
|
1057
|
+
} finally {
|
|
1058
|
+
client.release();
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
batchInsertUsers([
|
|
1063
|
+
{ name: 'Alice', email: 'alice@example.com', city: 'NYC' },
|
|
1064
|
+
{ name: 'Bob', email: 'bob@example.com', city: 'LA' }
|
|
1065
|
+
]);
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
---
|
|
1069
|
+
|
|
1070
|
+
## Schema Management
|
|
1071
|
+
|
|
1072
|
+
### Creating Tables
|
|
1073
|
+
|
|
1074
|
+
```javascript
|
|
1075
|
+
async function createUsersTable() {
|
|
1076
|
+
const client = await pool.connect();
|
|
1077
|
+
try {
|
|
1078
|
+
const query = `
|
|
1079
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
1080
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1081
|
+
email STRING UNIQUE NOT NULL,
|
|
1082
|
+
name STRING NOT NULL,
|
|
1083
|
+
city STRING,
|
|
1084
|
+
age INT,
|
|
1085
|
+
tags TEXT[],
|
|
1086
|
+
metadata JSONB,
|
|
1087
|
+
created_at TIMESTAMP DEFAULT now(),
|
|
1088
|
+
updated_at TIMESTAMP DEFAULT now()
|
|
1089
|
+
)
|
|
1090
|
+
`;
|
|
1091
|
+
await client.query(query);
|
|
1092
|
+
console.log('Users table created');
|
|
1093
|
+
} finally {
|
|
1094
|
+
client.release();
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
createUsersTable();
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
### Creating Indexes
|
|
1102
|
+
|
|
1103
|
+
```javascript
|
|
1104
|
+
async function createIndexes() {
|
|
1105
|
+
const client = await pool.connect();
|
|
1106
|
+
try {
|
|
1107
|
+
// Standard index
|
|
1108
|
+
await client.query('CREATE INDEX IF NOT EXISTS idx_users_city ON users (city)');
|
|
1109
|
+
|
|
1110
|
+
// Multi-column index
|
|
1111
|
+
await client.query('CREATE INDEX IF NOT EXISTS idx_users_city_age ON users (city, age)');
|
|
1112
|
+
|
|
1113
|
+
// Inverted index for JSONB
|
|
1114
|
+
await client.query('CREATE INVERTED INDEX IF NOT EXISTS idx_users_metadata ON users (metadata)');
|
|
1115
|
+
|
|
1116
|
+
// Inverted index for arrays
|
|
1117
|
+
await client.query('CREATE INVERTED INDEX IF NOT EXISTS idx_users_tags ON users (tags)');
|
|
1118
|
+
|
|
1119
|
+
console.log('Indexes created');
|
|
1120
|
+
} finally {
|
|
1121
|
+
client.release();
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
createIndexes();
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
### Altering Tables
|
|
1129
|
+
|
|
1130
|
+
```javascript
|
|
1131
|
+
async function alterUsersTable() {
|
|
1132
|
+
const client = await pool.connect();
|
|
1133
|
+
try {
|
|
1134
|
+
// Add column
|
|
1135
|
+
await client.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS status STRING DEFAULT \'active\'');
|
|
1136
|
+
|
|
1137
|
+
// Add constraint
|
|
1138
|
+
await client.query('ALTER TABLE users ADD CONSTRAINT check_age CHECK (age >= 0 AND age <= 150)');
|
|
1139
|
+
|
|
1140
|
+
console.log('Table altered successfully');
|
|
1141
|
+
} finally {
|
|
1142
|
+
client.release();
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
alterUsersTable();
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
---
|
|
1150
|
+
|
|
1151
|
+
## Error Handling
|
|
1152
|
+
|
|
1153
|
+
### Handling Specific Errors
|
|
1154
|
+
|
|
1155
|
+
```javascript
|
|
1156
|
+
async function createUserWithErrorHandling(email, name) {
|
|
1157
|
+
const client = await pool.connect();
|
|
1158
|
+
try {
|
|
1159
|
+
const query = 'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *';
|
|
1160
|
+
const result = await client.query(query, [email, name]);
|
|
1161
|
+
return result.rows[0];
|
|
1162
|
+
} catch (err) {
|
|
1163
|
+
if (err.code === '23505') {
|
|
1164
|
+
// Unique violation
|
|
1165
|
+
throw new Error(`User with email ${email} already exists`);
|
|
1166
|
+
} else if (err.code === '23502') {
|
|
1167
|
+
// Not null violation
|
|
1168
|
+
throw new Error('Required field is missing');
|
|
1169
|
+
} else if (err.code === '23503') {
|
|
1170
|
+
// Foreign key violation
|
|
1171
|
+
throw new Error('Referenced record does not exist');
|
|
1172
|
+
} else if (err.code === '40001') {
|
|
1173
|
+
// Serialization failure
|
|
1174
|
+
throw new Error('Transaction conflict, please retry');
|
|
1175
|
+
} else {
|
|
1176
|
+
throw err;
|
|
1177
|
+
}
|
|
1178
|
+
} finally {
|
|
1179
|
+
client.release();
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
createUserWithErrorHandling('john@example.com', 'John Doe');
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
### Connection Error Handling
|
|
1187
|
+
|
|
1188
|
+
```javascript
|
|
1189
|
+
async function queryWithRetry(query, params, maxRetries = 3) {
|
|
1190
|
+
let retries = 0;
|
|
1191
|
+
|
|
1192
|
+
while (retries < maxRetries) {
|
|
1193
|
+
let client;
|
|
1194
|
+
try {
|
|
1195
|
+
client = await pool.connect();
|
|
1196
|
+
const result = await client.query(query, params);
|
|
1197
|
+
client.release();
|
|
1198
|
+
return result;
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
if (client) client.release();
|
|
1201
|
+
|
|
1202
|
+
if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
|
|
1203
|
+
retries++;
|
|
1204
|
+
if (retries >= maxRetries) {
|
|
1205
|
+
throw new Error('Database connection failed after retries');
|
|
1206
|
+
}
|
|
1207
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * retries));
|
|
1208
|
+
} else {
|
|
1209
|
+
throw err;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
queryWithRetry('SELECT * FROM users WHERE id = $1', ['user-123']);
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
---
|
|
1219
|
+
|
|
1220
|
+
## Closing Connections
|
|
1221
|
+
|
|
1222
|
+
### Graceful Shutdown
|
|
1223
|
+
|
|
1224
|
+
```javascript
|
|
1225
|
+
async function gracefulShutdown() {
|
|
1226
|
+
console.log('Closing database connections...');
|
|
1227
|
+
await pool.end();
|
|
1228
|
+
console.log('Database connections closed');
|
|
1229
|
+
process.exit(0);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
1233
|
+
process.on('SIGINT', gracefulShutdown);
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
### Complete Application Example
|
|
1237
|
+
|
|
1238
|
+
```javascript
|
|
1239
|
+
const { Pool } = require('pg');
|
|
1240
|
+
|
|
1241
|
+
const pool = new Pool({
|
|
1242
|
+
user: process.env.DB_USER || 'root',
|
|
1243
|
+
host: process.env.DB_HOST || 'localhost',
|
|
1244
|
+
database: process.env.DB_NAME || 'defaultdb',
|
|
1245
|
+
port: parseInt(process.env.DB_PORT) || 26257,
|
|
1246
|
+
max: 20,
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
// Query functions
|
|
1250
|
+
async function getAllUsers() {
|
|
1251
|
+
const client = await pool.connect();
|
|
1252
|
+
try {
|
|
1253
|
+
const result = await client.query('SELECT * FROM users');
|
|
1254
|
+
return result.rows;
|
|
1255
|
+
} finally {
|
|
1256
|
+
client.release();
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
async function createUser(name, email) {
|
|
1261
|
+
const client = await pool.connect();
|
|
1262
|
+
try {
|
|
1263
|
+
const query = 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *';
|
|
1264
|
+
const result = await client.query(query, [name, email]);
|
|
1265
|
+
return result.rows[0];
|
|
1266
|
+
} finally {
|
|
1267
|
+
client.release();
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// Graceful shutdown
|
|
1272
|
+
async function shutdown() {
|
|
1273
|
+
await pool.end();
|
|
1274
|
+
process.exit(0);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
process.on('SIGTERM', shutdown);
|
|
1278
|
+
process.on('SIGINT', shutdown);
|
|
1279
|
+
|
|
1280
|
+
// Export for use in application
|
|
1281
|
+
module.exports = {
|
|
1282
|
+
pool,
|
|
1283
|
+
getAllUsers,
|
|
1284
|
+
createUser,
|
|
1285
|
+
};
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
---
|
|
1289
|
+
|
|
1290
|
+
## Common Patterns
|
|
1291
|
+
|
|
1292
|
+
### Repository Pattern
|
|
1293
|
+
|
|
1294
|
+
```javascript
|
|
1295
|
+
class UserRepository {
|
|
1296
|
+
constructor(pool) {
|
|
1297
|
+
this.pool = pool;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
async findAll() {
|
|
1301
|
+
const client = await this.pool.connect();
|
|
1302
|
+
try {
|
|
1303
|
+
const result = await client.query('SELECT * FROM users');
|
|
1304
|
+
return result.rows;
|
|
1305
|
+
} finally {
|
|
1306
|
+
client.release();
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
async findById(id) {
|
|
1311
|
+
const client = await this.pool.connect();
|
|
1312
|
+
try {
|
|
1313
|
+
const result = await client.query('SELECT * FROM users WHERE id = $1', [id]);
|
|
1314
|
+
return result.rows[0];
|
|
1315
|
+
} finally {
|
|
1316
|
+
client.release();
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
async create(user) {
|
|
1321
|
+
const client = await this.pool.connect();
|
|
1322
|
+
try {
|
|
1323
|
+
const query = `
|
|
1324
|
+
INSERT INTO users (name, email, city)
|
|
1325
|
+
VALUES ($1, $2, $3)
|
|
1326
|
+
RETURNING *
|
|
1327
|
+
`;
|
|
1328
|
+
const result = await client.query(query, [user.name, user.email, user.city]);
|
|
1329
|
+
return result.rows[0];
|
|
1330
|
+
} finally {
|
|
1331
|
+
client.release();
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
async update(id, updates) {
|
|
1336
|
+
const client = await this.pool.connect();
|
|
1337
|
+
try {
|
|
1338
|
+
const query = `
|
|
1339
|
+
UPDATE users
|
|
1340
|
+
SET name = $1, city = $2, updated_at = now()
|
|
1341
|
+
WHERE id = $3
|
|
1342
|
+
RETURNING *
|
|
1343
|
+
`;
|
|
1344
|
+
const result = await client.query(query, [updates.name, updates.city, id]);
|
|
1345
|
+
return result.rows[0];
|
|
1346
|
+
} finally {
|
|
1347
|
+
client.release();
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
async delete(id) {
|
|
1352
|
+
const client = await this.pool.connect();
|
|
1353
|
+
try {
|
|
1354
|
+
const result = await client.query('DELETE FROM users WHERE id = $1 RETURNING *', [id]);
|
|
1355
|
+
return result.rows[0];
|
|
1356
|
+
} finally {
|
|
1357
|
+
client.release();
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const userRepo = new UserRepository(pool);
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
### Query Builder Pattern
|
|
1366
|
+
|
|
1367
|
+
```javascript
|
|
1368
|
+
class QueryBuilder {
|
|
1369
|
+
constructor(pool, table) {
|
|
1370
|
+
this.pool = pool;
|
|
1371
|
+
this.table = table;
|
|
1372
|
+
this.whereConditions = [];
|
|
1373
|
+
this.parameters = [];
|
|
1374
|
+
this.orderByClause = '';
|
|
1375
|
+
this.limitClause = '';
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
where(column, operator, value) {
|
|
1379
|
+
this.parameters.push(value);
|
|
1380
|
+
this.whereConditions.push(`${column} ${operator} $${this.parameters.length}`);
|
|
1381
|
+
return this;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
orderBy(column, direction = 'ASC') {
|
|
1385
|
+
this.orderByClause = `ORDER BY ${column} ${direction}`;
|
|
1386
|
+
return this;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
limit(count) {
|
|
1390
|
+
this.limitClause = `LIMIT ${count}`;
|
|
1391
|
+
return this;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
async execute() {
|
|
1395
|
+
const client = await this.pool.connect();
|
|
1396
|
+
try {
|
|
1397
|
+
let query = `SELECT * FROM ${this.table}`;
|
|
1398
|
+
|
|
1399
|
+
if (this.whereConditions.length > 0) {
|
|
1400
|
+
query += ` WHERE ${this.whereConditions.join(' AND ')}`;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (this.orderByClause) {
|
|
1404
|
+
query += ` ${this.orderByClause}`;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
if (this.limitClause) {
|
|
1408
|
+
query += ` ${this.limitClause}`;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
const result = await client.query(query, this.parameters);
|
|
1412
|
+
return result.rows;
|
|
1413
|
+
} finally {
|
|
1414
|
+
client.release();
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// Usage
|
|
1420
|
+
const users = await new QueryBuilder(pool, 'users')
|
|
1421
|
+
.where('city', '=', 'Seattle')
|
|
1422
|
+
.where('age', '>=', 25)
|
|
1423
|
+
.orderBy('created_at', 'DESC')
|
|
1424
|
+
.limit(10)
|
|
1425
|
+
.execute();
|
|
1426
|
+
```
|
|
1427
|
+
|
|
1428
|
+
---
|
|
1429
|
+
|
|
1430
|
+
## Performance Optimization
|
|
1431
|
+
|
|
1432
|
+
### Connection Pooling Configuration
|
|
1433
|
+
|
|
1434
|
+
```javascript
|
|
1435
|
+
const { Pool } = require('pg');
|
|
1436
|
+
|
|
1437
|
+
const pool = new Pool({
|
|
1438
|
+
user: 'root',
|
|
1439
|
+
host: 'localhost',
|
|
1440
|
+
database: 'defaultdb',
|
|
1441
|
+
port: 26257,
|
|
1442
|
+
max: 20, // Maximum number of clients in the pool
|
|
1443
|
+
min: 5, // Minimum number of clients in the pool
|
|
1444
|
+
idleTimeoutMillis: 300000, // Close idle clients after 5 minutes (CockroachDB recommendation)
|
|
1445
|
+
connectionTimeoutMillis: 5000,
|
|
1446
|
+
allowExitOnIdle: false,
|
|
1447
|
+
});
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
### Prepared Statements
|
|
1451
|
+
|
|
1452
|
+
```javascript
|
|
1453
|
+
async function findUsersPrepared(city) {
|
|
1454
|
+
const client = await pool.connect();
|
|
1455
|
+
try {
|
|
1456
|
+
// Named prepared statement
|
|
1457
|
+
const queryName = 'find-users-by-city';
|
|
1458
|
+
const queryText = 'SELECT * FROM users WHERE city = $1';
|
|
1459
|
+
|
|
1460
|
+
const result = await client.query({
|
|
1461
|
+
name: queryName,
|
|
1462
|
+
text: queryText,
|
|
1463
|
+
values: [city]
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
return result.rows;
|
|
1467
|
+
} finally {
|
|
1468
|
+
client.release();
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
findUsersPrepared('Seattle');
|
|
1473
|
+
```
|
|
1474
|
+
|
|
1475
|
+
### Cursor-Based Pagination
|
|
1476
|
+
|
|
1477
|
+
```javascript
|
|
1478
|
+
async function paginateUsers(cursorId, pageSize = 20) {
|
|
1479
|
+
const client = await pool.connect();
|
|
1480
|
+
try {
|
|
1481
|
+
const query = `
|
|
1482
|
+
SELECT * FROM users
|
|
1483
|
+
WHERE id > $1
|
|
1484
|
+
ORDER BY id
|
|
1485
|
+
LIMIT $2
|
|
1486
|
+
`;
|
|
1487
|
+
const result = await client.query(query, [cursorId || '00000000-0000-0000-0000-000000000000', pageSize]);
|
|
1488
|
+
|
|
1489
|
+
return {
|
|
1490
|
+
data: result.rows,
|
|
1491
|
+
nextCursor: result.rows.length > 0 ? result.rows[result.rows.length - 1].id : null,
|
|
1492
|
+
hasMore: result.rows.length === pageSize
|
|
1493
|
+
};
|
|
1494
|
+
} finally {
|
|
1495
|
+
client.release();
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
paginateUsers(null, 20);
|
|
1500
|
+
```
|