autoworkflow 3.1.5 → 3.6.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/commands/analyze.md +19 -0
- package/.claude/commands/audit.md +26 -0
- package/.claude/commands/build.md +39 -0
- package/.claude/commands/commit.md +25 -0
- package/.claude/commands/fix.md +23 -0
- package/.claude/commands/plan.md +18 -0
- package/.claude/commands/suggest.md +23 -0
- package/.claude/commands/verify.md +18 -0
- package/.claude/hooks/post-bash-router.sh +20 -0
- package/.claude/hooks/post-commit.sh +140 -0
- package/.claude/hooks/post-edit.sh +190 -17
- package/.claude/hooks/pre-edit.sh +221 -0
- package/.claude/hooks/session-check.sh +90 -0
- package/.claude/settings.json +56 -6
- package/.claude/settings.local.json +5 -1
- package/.claude/skills/actix.md +337 -0
- package/.claude/skills/alembic.md +504 -0
- package/.claude/skills/angular.md +237 -0
- package/.claude/skills/api-design.md +187 -0
- package/.claude/skills/aspnet-core.md +377 -0
- package/.claude/skills/astro.md +245 -0
- package/.claude/skills/auth-clerk.md +327 -0
- package/.claude/skills/auth-firebase.md +367 -0
- package/.claude/skills/auth-nextauth.md +359 -0
- package/.claude/skills/auth-supabase.md +368 -0
- package/.claude/skills/axum.md +386 -0
- package/.claude/skills/blazor.md +456 -0
- package/.claude/skills/chi.md +348 -0
- package/.claude/skills/code-review.md +133 -0
- package/.claude/skills/csharp.md +296 -0
- package/.claude/skills/css-modules.md +325 -0
- package/.claude/skills/cypress.md +343 -0
- package/.claude/skills/debugging.md +133 -0
- package/.claude/skills/diesel.md +392 -0
- package/.claude/skills/django.md +301 -0
- package/.claude/skills/docker.md +319 -0
- package/.claude/skills/doctrine.md +473 -0
- package/.claude/skills/documentation.md +182 -0
- package/.claude/skills/dotnet.md +409 -0
- package/.claude/skills/drizzle.md +293 -0
- package/.claude/skills/echo.md +321 -0
- package/.claude/skills/eloquent.md +256 -0
- package/.claude/skills/emotion.md +426 -0
- package/.claude/skills/entity-framework.md +370 -0
- package/.claude/skills/express.md +316 -0
- package/.claude/skills/fastapi.md +329 -0
- package/.claude/skills/fastify.md +299 -0
- package/.claude/skills/fiber.md +315 -0
- package/.claude/skills/flask.md +322 -0
- package/.claude/skills/gin.md +342 -0
- package/.claude/skills/git.md +116 -0
- package/.claude/skills/github-actions.md +353 -0
- package/.claude/skills/go.md +377 -0
- package/.claude/skills/gorm.md +409 -0
- package/.claude/skills/graphql.md +478 -0
- package/.claude/skills/hibernate.md +379 -0
- package/.claude/skills/hono.md +306 -0
- package/.claude/skills/java.md +400 -0
- package/.claude/skills/jest.md +313 -0
- package/.claude/skills/jpa.md +282 -0
- package/.claude/skills/kotlin.md +347 -0
- package/.claude/skills/kubernetes.md +363 -0
- package/.claude/skills/laravel.md +414 -0
- package/.claude/skills/mcp-browser.md +320 -0
- package/.claude/skills/mcp-database.md +219 -0
- package/.claude/skills/mcp-fetch.md +241 -0
- package/.claude/skills/mcp-filesystem.md +204 -0
- package/.claude/skills/mcp-github.md +217 -0
- package/.claude/skills/mcp-memory.md +240 -0
- package/.claude/skills/mcp-search.md +218 -0
- package/.claude/skills/mcp-slack.md +262 -0
- package/.claude/skills/micronaut.md +388 -0
- package/.claude/skills/mongodb.md +319 -0
- package/.claude/skills/mongoose.md +355 -0
- package/.claude/skills/mysql.md +281 -0
- package/.claude/skills/nestjs.md +335 -0
- package/.claude/skills/nextjs-app-router.md +260 -0
- package/.claude/skills/nextjs-pages.md +172 -0
- package/.claude/skills/nuxt.md +202 -0
- package/.claude/skills/openapi.md +489 -0
- package/.claude/skills/performance.md +199 -0
- package/.claude/skills/php.md +398 -0
- package/.claude/skills/playwright.md +371 -0
- package/.claude/skills/postgresql.md +257 -0
- package/.claude/skills/prisma.md +293 -0
- package/.claude/skills/pydantic.md +304 -0
- package/.claude/skills/pytest.md +313 -0
- package/.claude/skills/python.md +272 -0
- package/.claude/skills/quarkus.md +377 -0
- package/.claude/skills/react.md +230 -0
- package/.claude/skills/redis.md +391 -0
- package/.claude/skills/refactoring.md +143 -0
- package/.claude/skills/remix.md +246 -0
- package/.claude/skills/rest-api.md +490 -0
- package/.claude/skills/rocket.md +366 -0
- package/.claude/skills/rust.md +341 -0
- package/.claude/skills/sass.md +380 -0
- package/.claude/skills/sea-orm.md +382 -0
- package/.claude/skills/security.md +167 -0
- package/.claude/skills/sequelize.md +395 -0
- package/.claude/skills/spring-boot.md +416 -0
- package/.claude/skills/sqlalchemy.md +269 -0
- package/.claude/skills/sqlx-rust.md +408 -0
- package/.claude/skills/state-jotai.md +346 -0
- package/.claude/skills/state-mobx.md +353 -0
- package/.claude/skills/state-pinia.md +431 -0
- package/.claude/skills/state-redux.md +337 -0
- package/.claude/skills/state-tanstack-query.md +434 -0
- package/.claude/skills/state-zustand.md +340 -0
- package/.claude/skills/styled-components.md +403 -0
- package/.claude/skills/svelte.md +238 -0
- package/.claude/skills/sveltekit.md +207 -0
- package/.claude/skills/symfony.md +437 -0
- package/.claude/skills/tailwind.md +279 -0
- package/.claude/skills/terraform.md +394 -0
- package/.claude/skills/testing-library.md +371 -0
- package/.claude/skills/trpc.md +426 -0
- package/.claude/skills/typeorm.md +368 -0
- package/.claude/skills/vitest.md +330 -0
- package/.claude/skills/vue.md +202 -0
- package/.claude/skills/warp.md +365 -0
- package/README.md +163 -52
- package/package.json +1 -1
- package/system/triggers.md +256 -17
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# SQLx Skill
|
|
2
|
+
|
|
3
|
+
## Setup and Configuration
|
|
4
|
+
\`\`\`bash
|
|
5
|
+
# Add to Cargo.toml
|
|
6
|
+
[dependencies]
|
|
7
|
+
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "uuid", "chrono", "migrate"] }
|
|
8
|
+
|
|
9
|
+
# Install sqlx-cli
|
|
10
|
+
cargo install sqlx-cli --no-default-features --features postgres
|
|
11
|
+
|
|
12
|
+
# Create database
|
|
13
|
+
sqlx database create
|
|
14
|
+
|
|
15
|
+
# Create migration
|
|
16
|
+
sqlx migrate add create_users
|
|
17
|
+
|
|
18
|
+
# Run migrations
|
|
19
|
+
sqlx migrate run
|
|
20
|
+
|
|
21
|
+
# Prepare for offline compile-time checking
|
|
22
|
+
cargo sqlx prepare
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
## Connection Pool
|
|
26
|
+
\`\`\`rust
|
|
27
|
+
use sqlx::postgres::{PgPool, PgPoolOptions};
|
|
28
|
+
use std::time::Duration;
|
|
29
|
+
|
|
30
|
+
pub async fn create_pool() -> Result<PgPool, sqlx::Error> {
|
|
31
|
+
let database_url = std::env::var("DATABASE_URL")
|
|
32
|
+
.expect("DATABASE_URL must be set");
|
|
33
|
+
|
|
34
|
+
PgPoolOptions::new()
|
|
35
|
+
.max_connections(100)
|
|
36
|
+
.min_connections(5)
|
|
37
|
+
.acquire_timeout(Duration::from_secs(10))
|
|
38
|
+
.idle_timeout(Duration::from_secs(300))
|
|
39
|
+
.connect(&database_url)
|
|
40
|
+
.await
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Run migrations at startup
|
|
44
|
+
pub async fn run_migrations(pool: &PgPool) -> Result<(), sqlx::migrate::MigrateError> {
|
|
45
|
+
sqlx::migrate!("./migrations")
|
|
46
|
+
.run(pool)
|
|
47
|
+
.await
|
|
48
|
+
}
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
## Model Definitions
|
|
52
|
+
\`\`\`rust
|
|
53
|
+
use chrono::{DateTime, Utc};
|
|
54
|
+
use serde::{Deserialize, Serialize};
|
|
55
|
+
use sqlx::FromRow;
|
|
56
|
+
use uuid::Uuid;
|
|
57
|
+
|
|
58
|
+
#[derive(Debug, Clone, FromRow, Serialize)]
|
|
59
|
+
pub struct User {
|
|
60
|
+
pub id: Uuid,
|
|
61
|
+
pub email: String,
|
|
62
|
+
pub name: String,
|
|
63
|
+
#[serde(skip_serializing)]
|
|
64
|
+
pub password_hash: String,
|
|
65
|
+
pub is_active: bool,
|
|
66
|
+
pub created_at: DateTime<Utc>,
|
|
67
|
+
pub updated_at: DateTime<Utc>,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[derive(Debug, Clone, FromRow, Serialize)]
|
|
71
|
+
pub struct Post {
|
|
72
|
+
pub id: Uuid,
|
|
73
|
+
pub title: String,
|
|
74
|
+
pub content: String,
|
|
75
|
+
pub published: bool,
|
|
76
|
+
pub author_id: Uuid,
|
|
77
|
+
pub created_at: DateTime<Utc>,
|
|
78
|
+
pub updated_at: DateTime<Utc>,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// For partial selects
|
|
82
|
+
#[derive(Debug, FromRow, Serialize)]
|
|
83
|
+
pub struct UserSummary {
|
|
84
|
+
pub id: Uuid,
|
|
85
|
+
pub email: String,
|
|
86
|
+
pub name: String,
|
|
87
|
+
}
|
|
88
|
+
\`\`\`
|
|
89
|
+
|
|
90
|
+
## Compile-Time Checked Queries (query_as!)
|
|
91
|
+
\`\`\`rust
|
|
92
|
+
use sqlx::PgPool;
|
|
93
|
+
|
|
94
|
+
pub struct UserRepository {
|
|
95
|
+
pool: PgPool,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
impl UserRepository {
|
|
99
|
+
pub fn new(pool: PgPool) -> Self {
|
|
100
|
+
Self { pool }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create - compile-time checked
|
|
104
|
+
pub async fn create(
|
|
105
|
+
&self,
|
|
106
|
+
email: &str,
|
|
107
|
+
name: &str,
|
|
108
|
+
password: &str,
|
|
109
|
+
) -> Result<User, AppError> {
|
|
110
|
+
let id = Uuid::new_v4();
|
|
111
|
+
let password_hash = hash_password(password)?;
|
|
112
|
+
let now = Utc::now();
|
|
113
|
+
|
|
114
|
+
sqlx::query_as!(
|
|
115
|
+
User,
|
|
116
|
+
r#"
|
|
117
|
+
INSERT INTO users (id, email, name, password_hash, is_active, created_at, updated_at)
|
|
118
|
+
VALUES ($1, $2, $3, $4, true, $5, $5)
|
|
119
|
+
RETURNING id, email, name, password_hash, is_active, created_at, updated_at
|
|
120
|
+
"#,
|
|
121
|
+
id,
|
|
122
|
+
email,
|
|
123
|
+
name,
|
|
124
|
+
password_hash,
|
|
125
|
+
now
|
|
126
|
+
)
|
|
127
|
+
.fetch_one(&self.pool)
|
|
128
|
+
.await
|
|
129
|
+
.map_err(AppError::from)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Find by ID
|
|
133
|
+
pub async fn find_by_id(&self, id: Uuid) -> Result<Option<User>, AppError> {
|
|
134
|
+
sqlx::query_as!(
|
|
135
|
+
User,
|
|
136
|
+
"SELECT id, email, name, password_hash, is_active, created_at, updated_at FROM users WHERE id = $1",
|
|
137
|
+
id
|
|
138
|
+
)
|
|
139
|
+
.fetch_optional(&self.pool)
|
|
140
|
+
.await
|
|
141
|
+
.map_err(AppError::from)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// List with pagination
|
|
145
|
+
pub async fn list(&self, page: i64, per_page: i64) -> Result<Vec<User>, AppError> {
|
|
146
|
+
let offset = (page - 1) * per_page;
|
|
147
|
+
|
|
148
|
+
sqlx::query_as!(
|
|
149
|
+
User,
|
|
150
|
+
r#"
|
|
151
|
+
SELECT id, email, name, password_hash, is_active, created_at, updated_at
|
|
152
|
+
FROM users
|
|
153
|
+
WHERE is_active = true
|
|
154
|
+
ORDER BY created_at DESC
|
|
155
|
+
LIMIT $1 OFFSET $2
|
|
156
|
+
"#,
|
|
157
|
+
per_page,
|
|
158
|
+
offset
|
|
159
|
+
)
|
|
160
|
+
.fetch_all(&self.pool)
|
|
161
|
+
.await
|
|
162
|
+
.map_err(AppError::from)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Count
|
|
166
|
+
pub async fn count_active(&self) -> Result<i64, AppError> {
|
|
167
|
+
let result = sqlx::query_scalar!(
|
|
168
|
+
"SELECT COUNT(*) FROM users WHERE is_active = true"
|
|
169
|
+
)
|
|
170
|
+
.fetch_one(&self.pool)
|
|
171
|
+
.await?;
|
|
172
|
+
|
|
173
|
+
Ok(result.unwrap_or(0))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Update
|
|
177
|
+
pub async fn update(
|
|
178
|
+
&self,
|
|
179
|
+
id: Uuid,
|
|
180
|
+
name: Option<&str>,
|
|
181
|
+
email: Option<&str>,
|
|
182
|
+
) -> Result<User, AppError> {
|
|
183
|
+
sqlx::query_as!(
|
|
184
|
+
User,
|
|
185
|
+
r#"
|
|
186
|
+
UPDATE users
|
|
187
|
+
SET
|
|
188
|
+
name = COALESCE($2, name),
|
|
189
|
+
email = COALESCE($3, email),
|
|
190
|
+
updated_at = NOW()
|
|
191
|
+
WHERE id = $1
|
|
192
|
+
RETURNING id, email, name, password_hash, is_active, created_at, updated_at
|
|
193
|
+
"#,
|
|
194
|
+
id,
|
|
195
|
+
name,
|
|
196
|
+
email
|
|
197
|
+
)
|
|
198
|
+
.fetch_one(&self.pool)
|
|
199
|
+
.await
|
|
200
|
+
.map_err(AppError::from)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Delete
|
|
204
|
+
pub async fn delete(&self, id: Uuid) -> Result<bool, AppError> {
|
|
205
|
+
let result = sqlx::query!("DELETE FROM users WHERE id = $1", id)
|
|
206
|
+
.execute(&self.pool)
|
|
207
|
+
.await?;
|
|
208
|
+
|
|
209
|
+
Ok(result.rows_affected() > 0)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
\`\`\`
|
|
213
|
+
|
|
214
|
+
## Dynamic Queries (query_as with QueryBuilder)
|
|
215
|
+
\`\`\`rust
|
|
216
|
+
use sqlx::QueryBuilder;
|
|
217
|
+
|
|
218
|
+
pub async fn search_users(
|
|
219
|
+
&self,
|
|
220
|
+
query: Option<&str>,
|
|
221
|
+
is_active: Option<bool>,
|
|
222
|
+
page: i64,
|
|
223
|
+
per_page: i64,
|
|
224
|
+
) -> Result<Vec<User>, AppError> {
|
|
225
|
+
let mut builder = QueryBuilder::new(
|
|
226
|
+
"SELECT id, email, name, password_hash, is_active, created_at, updated_at FROM users WHERE 1=1"
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
if let Some(q) = query {
|
|
230
|
+
builder.push(" AND (name ILIKE ");
|
|
231
|
+
builder.push_bind(format!("%{}%", q));
|
|
232
|
+
builder.push(" OR email ILIKE ");
|
|
233
|
+
builder.push_bind(format!("%{}%", q));
|
|
234
|
+
builder.push(")");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if let Some(active) = is_active {
|
|
238
|
+
builder.push(" AND is_active = ");
|
|
239
|
+
builder.push_bind(active);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
builder.push(" ORDER BY created_at DESC");
|
|
243
|
+
builder.push(" LIMIT ");
|
|
244
|
+
builder.push_bind(per_page);
|
|
245
|
+
builder.push(" OFFSET ");
|
|
246
|
+
builder.push_bind((page - 1) * per_page);
|
|
247
|
+
|
|
248
|
+
builder
|
|
249
|
+
.build_query_as::<User>()
|
|
250
|
+
.fetch_all(&self.pool)
|
|
251
|
+
.await
|
|
252
|
+
.map_err(AppError::from)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Bulk insert
|
|
256
|
+
pub async fn bulk_create(&self, users: Vec<NewUser>) -> Result<Vec<User>, AppError> {
|
|
257
|
+
let mut builder = QueryBuilder::new(
|
|
258
|
+
"INSERT INTO users (id, email, name, password_hash, is_active, created_at, updated_at) "
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
builder.push_values(users, |mut b, user| {
|
|
262
|
+
b.push_bind(Uuid::new_v4())
|
|
263
|
+
.push_bind(&user.email)
|
|
264
|
+
.push_bind(&user.name)
|
|
265
|
+
.push_bind(&user.password_hash)
|
|
266
|
+
.push_bind(true)
|
|
267
|
+
.push_bind(Utc::now())
|
|
268
|
+
.push_bind(Utc::now());
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
builder.push(" RETURNING id, email, name, password_hash, is_active, created_at, updated_at");
|
|
272
|
+
|
|
273
|
+
builder
|
|
274
|
+
.build_query_as::<User>()
|
|
275
|
+
.fetch_all(&self.pool)
|
|
276
|
+
.await
|
|
277
|
+
.map_err(AppError::from)
|
|
278
|
+
}
|
|
279
|
+
\`\`\`
|
|
280
|
+
|
|
281
|
+
## Transactions
|
|
282
|
+
\`\`\`rust
|
|
283
|
+
use sqlx::Acquire;
|
|
284
|
+
|
|
285
|
+
pub async fn create_user_with_profile(
|
|
286
|
+
&self,
|
|
287
|
+
email: &str,
|
|
288
|
+
name: &str,
|
|
289
|
+
password: &str,
|
|
290
|
+
bio: &str,
|
|
291
|
+
) -> Result<(User, Profile), AppError> {
|
|
292
|
+
let mut tx = self.pool.begin().await?;
|
|
293
|
+
|
|
294
|
+
let user = sqlx::query_as!(
|
|
295
|
+
User,
|
|
296
|
+
r#"
|
|
297
|
+
INSERT INTO users (id, email, name, password_hash, is_active, created_at, updated_at)
|
|
298
|
+
VALUES ($1, $2, $3, $4, true, NOW(), NOW())
|
|
299
|
+
RETURNING id, email, name, password_hash, is_active, created_at, updated_at
|
|
300
|
+
"#,
|
|
301
|
+
Uuid::new_v4(),
|
|
302
|
+
email,
|
|
303
|
+
name,
|
|
304
|
+
hash_password(password)?
|
|
305
|
+
)
|
|
306
|
+
.fetch_one(&mut *tx)
|
|
307
|
+
.await?;
|
|
308
|
+
|
|
309
|
+
let profile = sqlx::query_as!(
|
|
310
|
+
Profile,
|
|
311
|
+
r#"
|
|
312
|
+
INSERT INTO profiles (id, user_id, bio)
|
|
313
|
+
VALUES ($1, $2, $3)
|
|
314
|
+
RETURNING id, user_id, bio
|
|
315
|
+
"#,
|
|
316
|
+
Uuid::new_v4(),
|
|
317
|
+
user.id,
|
|
318
|
+
bio
|
|
319
|
+
)
|
|
320
|
+
.fetch_one(&mut *tx)
|
|
321
|
+
.await?;
|
|
322
|
+
|
|
323
|
+
tx.commit().await?;
|
|
324
|
+
|
|
325
|
+
Ok((user, profile))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Transaction with savepoint
|
|
329
|
+
pub async fn complex_operation(&self) -> Result<(), AppError> {
|
|
330
|
+
let mut tx = self.pool.begin().await?;
|
|
331
|
+
|
|
332
|
+
// First operation
|
|
333
|
+
create_user(&mut *tx, user1).await?;
|
|
334
|
+
|
|
335
|
+
// Nested transaction (savepoint)
|
|
336
|
+
let savepoint = tx.begin().await?;
|
|
337
|
+
match create_user(&mut *savepoint, user2).await {
|
|
338
|
+
Ok(_) => savepoint.commit().await?,
|
|
339
|
+
Err(e) => {
|
|
340
|
+
// Savepoint rolled back, outer transaction continues
|
|
341
|
+
tracing::warn!("Savepoint failed: {:?}", e);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
tx.commit().await?;
|
|
346
|
+
Ok(())
|
|
347
|
+
}
|
|
348
|
+
\`\`\`
|
|
349
|
+
|
|
350
|
+
## Streaming Results
|
|
351
|
+
\`\`\`rust
|
|
352
|
+
use futures::TryStreamExt;
|
|
353
|
+
|
|
354
|
+
pub async fn process_all_users(&self) -> Result<(), AppError> {
|
|
355
|
+
let mut stream = sqlx::query_as!(
|
|
356
|
+
User,
|
|
357
|
+
"SELECT id, email, name, password_hash, is_active, created_at, updated_at FROM users"
|
|
358
|
+
)
|
|
359
|
+
.fetch(&self.pool);
|
|
360
|
+
|
|
361
|
+
while let Some(user) = stream.try_next().await? {
|
|
362
|
+
// Process each user without loading all into memory
|
|
363
|
+
process_user(&user).await?;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
Ok(())
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// With batching
|
|
370
|
+
pub async fn export_users(&self) -> Result<(), AppError> {
|
|
371
|
+
let mut stream = sqlx::query_as!(User, "SELECT * FROM users")
|
|
372
|
+
.fetch(&self.pool);
|
|
373
|
+
|
|
374
|
+
let mut batch = Vec::with_capacity(100);
|
|
375
|
+
|
|
376
|
+
while let Some(user) = stream.try_next().await? {
|
|
377
|
+
batch.push(user);
|
|
378
|
+
|
|
379
|
+
if batch.len() >= 100 {
|
|
380
|
+
export_batch(&batch).await?;
|
|
381
|
+
batch.clear();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Process remaining
|
|
386
|
+
if !batch.is_empty() {
|
|
387
|
+
export_batch(&batch).await?;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
Ok(())
|
|
391
|
+
}
|
|
392
|
+
\`\`\`
|
|
393
|
+
|
|
394
|
+
## ✅ DO
|
|
395
|
+
- Use \`query_as!\` for compile-time checked queries
|
|
396
|
+
- Use \`query_scalar!\` for single values
|
|
397
|
+
- Use \`fetch_optional\` for queries that may return no results
|
|
398
|
+
- Use transactions for multi-step operations
|
|
399
|
+
- Use \`QueryBuilder\` for dynamic queries
|
|
400
|
+
- Run \`cargo sqlx prepare\` before building for offline mode
|
|
401
|
+
- Use streaming for large result sets
|
|
402
|
+
|
|
403
|
+
## ❌ DON'T
|
|
404
|
+
- Don't use \`fetch_one\` unless you're certain a row exists
|
|
405
|
+
- Don't forget to handle \`sqlx::Error\` properly
|
|
406
|
+
- Don't build SQL strings manually - use \`QueryBuilder\`
|
|
407
|
+
- Don't hold transactions longer than necessary
|
|
408
|
+
- Don't forget \`&mut *tx\` when passing transaction to queries
|