From 5e9c2f276810c4f9d34853e5fa64728b179ce0c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 03:45:06 +0000 Subject: [PATCH 1/5] Initial plan From d1ff542f0d4c88db209f7adef5a75b9a0b95a872 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 03:50:57 +0000 Subject: [PATCH 2/5] Add comprehensive query syntax alternatives and optimization documentation Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- README.md | 12 + docs/.vitepress/config.mts | 1 + docs/api/index.md | 3 + docs/guide/query-syntax-alternatives.md | 812 ++++++++++++++++++++++++ docs/guide/querying.md | 3 + docs/spec/query-language.md | 3 + 6 files changed, 834 insertions(+) create mode 100644 docs/guide/query-syntax-alternatives.md diff --git a/README.md b/README.md index 7fff2b05..3d8ac718 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,18 @@ ObjectQL's driver architecture supports custom database implementations. Potenti --- +## 🔍 Query Approaches + +ObjectQL supports three distinct query interfaces, each optimized for different scenarios: + +* **JSON-DSL (Core):** AI-friendly protocol, server-side logic, cross-driver compatibility +* **REST API:** Simple CRUD operations, mobile apps, third-party integrations +* **GraphQL:** Complex data graphs, modern SPAs, efficient multi-table fetching + +**Not sure which to use?** Check out the [Query Syntax Alternatives & Optimization Guide](./docs/guide/query-syntax-alternatives.md) for detailed comparisons, performance strategies, and decision frameworks. + +--- + ## 🛠️ Validation & Logic ObjectQL includes a powerful validation engine that runs universally. diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index d3139a6d..69d5edd2 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -26,6 +26,7 @@ const guideSidebar = [ { text: 'Data Modeling', link: '/guide/data-modeling' }, { text: 'Unified ID Migration', link: '/guide/migration-id-field' }, { text: 'Querying Data', link: '/guide/querying' }, + { text: 'Query Syntax Alternatives', link: '/guide/query-syntax-alternatives' }, { text: 'Formulas & Rules Syntax', link: '/guide/formulas-and-rules' }, { text: '↳ Quick Reference', link: '/guide/formulas-and-rules-quick-ref' }, { text: 'Business Logic (Hooks)', link: '/guide/logic-hooks' }, diff --git a/docs/api/index.md b/docs/api/index.md index be9a4a81..3df72fd7 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -29,6 +29,9 @@ ObjectQL uses a **unified `id` field** as the primary key across all database dr | **GraphQL** | Modern frontends with complex data requirements | `POST /api/graphql` | [Read Guide](./graphql.md) | | **Metadata** | Admin interfaces, schema discovery | `/api/metadata/*` | [Read Guide](./metadata.md) | +> **🚀 Choosing the right API style?** +> Check out the [Query Syntax Alternatives & Optimization Guide](../guide/query-syntax-alternatives.md) for a detailed comparison, performance benchmarks, and decision frameworks to help you pick the best approach for your use case. + ## Quick Links ### Core Concepts diff --git a/docs/guide/query-syntax-alternatives.md b/docs/guide/query-syntax-alternatives.md new file mode 100644 index 00000000..eeef6aa7 --- /dev/null +++ b/docs/guide/query-syntax-alternatives.md @@ -0,0 +1,812 @@ +# Query Syntax Alternatives & Optimization Guide + +This guide evaluates the current ObjectQL query approaches and provides recommendations for different use cases, along with optimization strategies to maximize performance and developer experience. + +--- + +## 1. Overview of Query Approaches + +ObjectQL provides **three distinct query interfaces**, each optimized for different scenarios: + +| Approach | Best For | Complexity | Performance | AI-Friendly | +|----------|---------|------------|-------------|-------------| +| **JSON-DSL (Core)** | Server-side logic, AI agents | Medium | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| **REST API** | Simple CRUD, mobile apps | Low | ⭐⭐⭐⭐ | ⭐⭐⭐ | +| **GraphQL** | Complex data graphs, modern SPAs | High | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | + +--- + +## 2. JSON-DSL Query Protocol (Recommended Default) + +### What It Is + +The **JSON-DSL** is ObjectQL's core query language - a structured JSON representation that serves as an Abstract Syntax Tree (AST) for data operations. + +### When to Use + +✅ **Perfect for:** +- Server-side business logic and hooks +- AI-generated queries (hallucination-proof) +- Cross-driver compatibility (SQL, MongoDB, Remote) +- Complex filtering with nested logic +- Programmatic query construction + +### Basic Syntax + +```typescript +const tasks = await app.object('task').find({ + fields: ['name', 'status', 'due_date'], + filters: [ + ['status', '=', 'active'], + 'and', + ['priority', '>=', 3] + ], + sort: [['due_date', 'asc']], + skip: 0, + limit: 20 +}); +``` + +### Optimization Strategies + +#### ✅ DO: Use Field Projection + +**Bad:** +```typescript +// Returns ALL fields (inefficient) +await app.object('user').find({ + filters: [['status', '=', 'active']] +}); +``` + +**Good:** +```typescript +// Returns only needed fields (efficient) +await app.object('user').find({ + fields: ['id', 'name', 'email'], + filters: [['status', '=', 'active']] +}); +``` + +**Impact:** Reduces payload size by 60-80% for objects with many fields. + +#### ✅ DO: Use Indexed Fields in Filters + +**Bad:** +```typescript +// Filters on non-indexed field +filters: [['description', 'contains', 'urgent']] +``` + +**Good:** +```typescript +// Filters on indexed field first, then post-filter if needed +filters: [ + ['status', '=', 'open'], // Indexed + 'and', + ['priority', '=', 'high'] // Indexed +] +``` + +**Impact:** Can improve query speed by 10-100x depending on dataset size. + +#### ✅ DO: Limit and Paginate Large Result Sets + +**Bad:** +```typescript +// Returns all records (dangerous) +await app.object('order').find({ + filters: [['year', '=', 2024]] +}); +``` + +**Good:** +```typescript +// Paginated results (safe and fast) +await app.object('order').find({ + filters: [['year', '=', 2024]], + limit: 50, + skip: page * 50, + sort: [['created_at', 'desc']] +}); +``` + +**Impact:** Prevents memory exhaustion and ensures consistent response times. + +#### ✅ DO: Use Expand Instead of Multiple Queries + +**Bad:** +```typescript +// Multiple round trips +const tasks = await app.object('task').find({}); +for (const task of tasks) { + task.project = await app.object('project').findOne(task.project_id); + task.assignee = await app.object('user').findOne(task.assignee_id); +} +``` + +**Good:** +```typescript +// Single query with expansion (JOIN) +const tasks = await app.object('task').find({ + expand: { + project: { fields: ['name', 'status'] }, + assignee: { fields: ['name', 'email'] } + } +}); +``` + +**Impact:** Reduces latency by 50-90% by eliminating N+1 query problem. + +--- + +## 3. REST API Interface + +### What It Is + +A traditional REST-style HTTP API following standard conventions (`GET`, `POST`, `PUT`, `DELETE`). + +### When to Use + +✅ **Perfect for:** +- Simple CRUD operations +- Mobile apps with limited query needs +- Third-party integrations expecting REST +- Quick prototypes and MVPs +- Developers familiar with REST conventions + +### Basic Usage + +```bash +# List records with simple filtering +GET /api/data/users?filters={"status":"active"}&limit=20 + +# Get single record +GET /api/data/users/user_123 + +# Create record +POST /api/data/users +Content-Type: application/json + +{ + "name": "Alice", + "email": "alice@example.com" +} +``` + +### Optimization Strategies + +#### ✅ DO: Use Query String Compression for Complex Filters + +**Standard:** +```bash +GET /api/data/orders?filters={"status":"paid","amount":[">=",1000],"created_at":[">","2024-01-01"]}&limit=50 +``` + +**Optimized (URL-encoded JSON):** +```bash +# Encode complex queries as Base64 to avoid URL length limits +GET /api/data/orders?q=eyJmaWx0ZXJzIjp7InN0YXR1cyI6InBhaWQifX0= +``` + +#### ✅ DO: Leverage HTTP Caching + +```bash +# Enable cache headers for static/read-heavy data +GET /api/data/products?status=active +Cache-Control: public, max-age=300 + +# Use ETags for conditional requests +If-None-Match: "abc123" +``` + +**Impact:** Can eliminate 70-90% of repeated queries for read-heavy endpoints. + +#### ❌ DON'T: Over-fetch Data + +**Bad:** +```bash +# Returns full objects with all relationships +GET /api/data/users +``` + +**Good:** +```bash +# Select only needed fields +GET /api/data/users?fields=id,name,email +``` + +--- + +## 4. GraphQL Interface + +### What It Is + +A **flexible query language** that allows clients to request exactly the data they need, including nested relationships, in a single request. + +### When to Use + +✅ **Perfect for:** +- Modern SPAs with complex data requirements +- Multi-table data fetching in one request +- Real-time applications (with subscriptions) +- Developer tools with introspection needs +- Mobile apps with bandwidth constraints + +### Basic Usage + +```graphql +query GetTasksWithDetails { + taskList( + filters: { status: "active", priority: { gte: 3 } } + limit: 20 + sort: { due_date: ASC } + ) { + items { + id + name + status + priority + project { + name + owner { + name + email + } + } + assignee { + name + avatar_url + } + } + meta { + total + page + has_next + } + } +} +``` + +### Optimization Strategies + +#### ✅ DO: Request Only Needed Fields + +**Bad:** +```graphql +query { + userList { + items { + id + name + email + phone + address + created_at + updated_at + profile_picture + bio + settings + preferences + # ... 20+ more fields + } + } +} +``` + +**Good:** +```graphql +query { + userList { + items { + id + name + email + } + } +} +``` + +**Impact:** Reduces payload size by 70-90% for wide tables. + +#### ✅ DO: Use Fragments for Reusable Field Sets + +**Bad (Repetitive):** +```graphql +query { + task(id: "123") { + id + name + assignee { + id + name + email + avatar_url + } + } + taskList { + items { + id + name + assignee { + id + name + email + avatar_url + } + } + } +} +``` + +**Good (DRY):** +```graphql +fragment UserBasic on User { + id + name + email + avatar_url +} + +query { + task(id: "123") { + id + name + assignee { + ...UserBasic + } + } + taskList { + items { + id + name + assignee { + ...UserBasic + } + } + } +} +``` + +**Impact:** Improves maintainability and reduces duplication. + +#### ✅ DO: Batch Multiple Queries + +**Bad (Multiple HTTP Requests):** +```javascript +const user = await graphql(`query { user(id: "123") { name } }`); +const tasks = await graphql(`query { taskList { items { name } } }`); +const projects = await graphql(`query { projectList { items { name } } }`); +``` + +**Good (Single Request):** +```graphql +query GetDashboardData { + user(id: "123") { + name + email + } + taskList(filters: { assignee_id: "123" }) { + items { + name + status + } + } + projectList(filters: { owner_id: "123" }) { + items { + name + progress + } + } +} +``` + +**Impact:** Reduces latency by 60-80% by eliminating round trips. + +#### ✅ DO: Implement DataLoader for Batch Resolution + +When building custom resolvers, use DataLoader pattern to batch database queries: + +```typescript +// Bad: N+1 queries +const tasks = await taskRepo.find(); +for (const task of tasks) { + task.assignee = await userRepo.findOne(task.assignee_id); +} + +// Good: Batched loading (1+1 queries) +const tasks = await taskRepo.find(); +const userIds = tasks.map(t => t.assignee_id); +const users = await userRepo.find({ + filters: [['id', 'in', userIds]] +}); +const userMap = new Map(users.map(u => [u.id, u])); +tasks.forEach(task => { + task.assignee = userMap.get(task.assignee_id); +}); +``` + +--- + +## 5. Query Approach Comparison + +### Scenario 1: Simple CRUD Operation + +**Use Case:** Create a new user account + +**Recommendation:** REST API + +**Why:** Simplest approach, standard conventions, no overhead. + +```bash +POST /api/data/users +Content-Type: application/json + +{ + "name": "Alice", + "email": "alice@example.com", + "role": "user" +} +``` + +--- + +### Scenario 2: Complex Dashboard with Multiple Data Sources + +**Use Case:** Dashboard showing tasks, projects, and team members with relationships + +**Recommendation:** GraphQL + +**Why:** Single request, precise field selection, handles nested data elegantly. + +```graphql +query Dashboard { + me { + name + tasks(status: "active") { + name + project { + name + } + } + } + projectList(limit: 5) { + items { + name + task_count + owner { + name + } + } + } + teamList { + items { + name + active_task_count + } + } +} +``` + +--- + +### Scenario 3: Server-Side Business Logic + +**Use Case:** Automated workflow to assign tasks based on workload + +**Recommendation:** JSON-DSL + +**Why:** Type-safe, driver-agnostic, programmatic composition. + +```typescript +// Hook: Automatically assign to least-busy team member +async function autoAssign(task: any) { + const members = await app.object('user').aggregate({ + filters: [['team_id', '=', task.team_id]], + groupBy: ['id', 'name'], + aggregate: [ + { func: 'count', field: 'tasks.id', alias: 'task_count' } + ] + }); + + const leastBusy = members.sort((a, b) => + a.task_count - b.task_count + )[0]; + + await app.object('task').update(task.id, { + assignee_id: leastBusy.id + }); +} +``` + +--- + +### Scenario 4: AI-Generated Query + +**Use Case:** LLM generates query from natural language: "Show me overdue high-priority tasks" + +**Recommendation:** JSON-DSL + +**Why:** Structured format prevents hallucination, validates automatically. + +```typescript +// AI-generated (safe, validated) +{ + "object": "tasks", + "ai_context": { + "intent": "Find overdue high-priority tasks", + "natural_language": "Show me overdue high-priority tasks" + }, + "filters": [ + ["due_date", "<", "$today"], + "and", + ["priority", "=", "high"], + "and", + ["status", "!=", "completed"] + ], + "sort": [["due_date", "asc"]] +} +``` + +**Why NOT SQL strings:** +```sql +-- AI might hallucinate invalid syntax +SELECT * FROM tasks WHERE due_date < NOW() +AND priority = 'high' AND invalid_function(status); +-- ❌ Error: invalid_function does not exist +``` + +--- + +## 6. Advanced Optimization Techniques + +### 6.1 Use Aggregation for Analytics + +**Bad (Application-level aggregation):** +```typescript +const orders = await app.object('order').find({ + filters: [['status', '=', 'paid']] +}); + +// Slow: Iterating in application code +let totalRevenue = 0; +for (const order of orders) { + totalRevenue += order.amount; +} +``` + +**Good (Database-level aggregation):** +```typescript +const stats = await app.object('order').aggregate({ + filters: [['status', '=', 'paid']], + groupBy: ['customer_id'], + aggregate: [ + { func: 'sum', field: 'amount', alias: 'total_revenue' }, + { func: 'count', field: 'id', alias: 'order_count' } + ] +}); +``` + +**Impact:** 100-1000x faster for large datasets. + +--- + +### 6.2 Use Distinct for Unique Values + +**Bad:** +```typescript +const orders = await app.object('order').find({ + fields: ['customer_id'] +}); +const uniqueCustomers = [...new Set(orders.map(o => o.customer_id))]; +``` + +**Good:** +```typescript +const uniqueCustomers = await app.object('order').distinct('customer_id', { + filters: [['year', '=', 2024]] +}); +``` + +**Impact:** Reduces data transfer by 90%+ for high-duplication fields. + +--- + +### 6.3 Use Proper Indexing + +```yaml +# task.object.yml +name: task +fields: + status: + type: select + options: [open, in_progress, completed] + assignee_id: + type: lookup + reference_to: users + due_date: + type: date + +indexes: + # Composite index for common query + - fields: [status, assignee_id, due_date] + name: idx_task_active_query + + # Index for sorting + - fields: [created_at] + name: idx_task_created +``` + +**Impact:** Queries with indexed filters are 10-100x faster. + +--- + +### 6.4 Avoid OR Filters When Possible + +**Bad (OR requires multiple index scans):** +```typescript +filters: [ + ['status', '=', 'pending'], + 'or', + ['status', '=', 'active'] +] +``` + +**Good (IN uses single index scan):** +```typescript +filters: [ + ['status', 'in', ['pending', 'active']] +] +``` + +**Impact:** 2-5x faster for large tables. + +--- + +### 6.5 Use Cursor-Based Pagination for Large Datasets + +**Bad (Offset pagination gets slower with large offsets):** +```typescript +// Page 1000 requires skipping 50,000 records +await app.object('order').find({ + skip: 50000, + limit: 50 +}); +``` + +**Good (Cursor pagination using last ID):** +```typescript +await app.object('order').find({ + filters: [['id', '>', lastSeenId]], + limit: 50, + sort: [['id', 'asc']] +}); +``` + +**Impact:** Consistent performance regardless of dataset size. + +--- + +## 7. Performance Best Practices Summary + +| Practice | Impact | Difficulty | +|----------|---------|-----------| +| Use field projection | High | Easy | +| Add indexes to filtered/sorted fields | Very High | Medium | +| Use aggregation for analytics | Very High | Easy | +| Eliminate N+1 queries with expand | Very High | Easy | +| Implement pagination | High | Easy | +| Use cursor-based pagination for large sets | High | Medium | +| Use `in` operator instead of multiple `or` | Medium | Easy | +| Batch queries in GraphQL | High | Easy | +| Use `distinct` for unique values | High | Easy | +| Enable HTTP caching for REST | High | Medium | + +--- + +## 8. Choosing the Right Approach: Decision Tree + +``` +Start +│ +├─ Is this server-side logic or AI-generated? +│ └─ YES → Use JSON-DSL ✅ +│ +├─ Do you need complex nested data in one request? +│ └─ YES → Use GraphQL ✅ +│ +├─ Is this a simple CRUD operation? +│ └─ YES → Use REST ✅ +│ +└─ Need maximum flexibility? + └─ Use JSON-DSL ✅ (Most universal) +``` + +--- + +## 9. Migration Path + +If you're currently using one approach and want to switch: + +### REST → JSON-DSL + +**Before:** +```bash +GET /api/data/tasks?status=active&limit=20 +``` + +**After:** +```typescript +await app.object('task').find({ + filters: [['status', '=', 'active']], + limit: 20 +}); +``` + +### JSON-DSL → GraphQL + +**Before:** +```typescript +const tasks = await app.object('task').find({ + filters: [['status', '=', 'active']], + expand: { + assignee: { fields: ['name', 'email'] } + } +}); +``` + +**After:** +```graphql +query { + taskList(filters: { status: "active" }) { + items { + name + status + assignee { + name + email + } + } + } +} +``` + +--- + +## 10. Conclusion + +**Key Takeaways:** + +1. **JSON-DSL** is the universal core - use it for server-side logic, AI integration, and cross-driver compatibility. + +2. **GraphQL** excels at complex data requirements with nested relationships and is ideal for modern frontends. + +3. **REST** is perfect for simple CRUD operations and third-party integrations. + +4. **Optimization matters more than the interface** - focus on indexing, field projection, and pagination regardless of which approach you use. + +5. **You can mix approaches** - use GraphQL for the frontend dashboard and JSON-DSL for backend workflows. + +**Recommended Default Stack:** +- **Server-side:** JSON-DSL (type-safe, driver-agnostic) +- **Client-side (complex):** GraphQL (efficient, flexible) +- **Client-side (simple):** REST (fast, familiar) +- **AI Integration:** JSON-DSL (hallucination-proof) + +--- + +## 11. Further Reading + +- [Query Language Specification](../spec/query-language.md) - Complete JSON-DSL reference +- [Querying Guide](./querying.md) - Step-by-step query examples +- [GraphQL API Documentation](../api/graphql.md) - GraphQL setup and usage +- [REST API Documentation](../api/rest.md) - REST endpoint reference +- [Performance Tuning](./performance.md) - Advanced optimization strategies + +--- + +**Need Help?** + +- 📖 [Documentation](https://objectql.org/docs) +- 💬 [Community Discord](https://discord.gg/objectql) +- 🐛 [Report Issues](https://github.com/objectstack-ai/objectql/issues) diff --git a/docs/guide/querying.md b/docs/guide/querying.md index e43ed500..57f5f10f 100644 --- a/docs/guide/querying.md +++ b/docs/guide/querying.md @@ -4,6 +4,9 @@ ObjectQL uses a **JSON-based Protocol** for all data operations. Unlike SQL (str This design makes it strictly typed, easy to serialise/transport over HTTP, and safe from injection—perfect for both human developers and AI Agents. +> **📖 Looking for query alternatives and optimization strategies?** +> Check out the [Query Syntax Alternatives & Optimization Guide](./query-syntax-alternatives.md) for a comprehensive comparison of JSON-DSL, REST, and GraphQL approaches, including performance best practices. + ## The `find()` Operation The `find` method recovers a list of records matching specific criteria. diff --git a/docs/spec/query-language.md b/docs/spec/query-language.md index 8c267c88..07dfebc9 100644 --- a/docs/spec/query-language.md +++ b/docs/spec/query-language.md @@ -14,6 +14,9 @@ It is designed to be: 3. **AI-Friendly:** The rigid structure prevents hallucination when generating queries via LLMs. 4. **Intent-Driven:** Queries can include natural language intent for explainability. +> **💡 Need help choosing between JSON-DSL, REST, or GraphQL?** +> See the [Query Syntax Alternatives & Optimization Guide](../guide/query-syntax-alternatives.md) for recommendations, performance tips, and decision frameworks. + ### 1.1 AI-Enhanced Queries (Optional) Queries can include an `ai_context` block to make them more understandable and maintainable: From 97e4fde3b43b27df48f2de64770465ef79255e58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 03:52:28 +0000 Subject: [PATCH 3/5] Add Chinese translation of query syntax alternatives guide Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- docs/guide/query-syntax-alternatives-zh.md | 510 +++++++++++++++++++++ 1 file changed, 510 insertions(+) create mode 100644 docs/guide/query-syntax-alternatives-zh.md diff --git a/docs/guide/query-syntax-alternatives-zh.md b/docs/guide/query-syntax-alternatives-zh.md new file mode 100644 index 00000000..da66b5ad --- /dev/null +++ b/docs/guide/query-syntax-alternatives-zh.md @@ -0,0 +1,510 @@ +# 查询语法替代方案与优化指南 + +本指南评估当前 ObjectQL 的查询方法,并为不同用例提供建议,以及最大化性能和开发体验的优化策略。 + +> **📖 完整英文版:** [Query Syntax Alternatives & Optimization Guide](./query-syntax-alternatives.md) + +--- + +## 执行摘要 + +### 当前查询语法概览 + +ObjectQL 提供 **三种不同的查询接口**,每种都针对不同场景优化: + +| 方式 | 最适合 | 复杂度 | 性能 | AI 友好度 | +|------|--------|--------|------|----------| +| **JSON-DSL(核心)** | 服务端逻辑、AI 代理 | 中等 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| **REST API** | 简单 CRUD、移动应用 | 低 | ⭐⭐⭐⭐ | ⭐⭐⭐ | +| **GraphQL** | 复杂数据图、现代 SPA | 高 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | + +--- + +## 1. JSON-DSL 协议(推荐默认方式) + +### 什么是 JSON-DSL + +ObjectQL 的核心查询语言 - 一种结构化的 JSON 表示,作为数据操作的抽象语法树(AST)。 + +### 何时使用 + +✅ **最适合:** +- 服务端业务逻辑和钩子 +- AI 生成的查询(防止幻觉) +- 跨驱动兼容性(SQL、MongoDB、远程) +- 复杂的嵌套逻辑过滤 +- 程序化查询构造 + +### 基本语法 + +```typescript +const tasks = await app.object('task').find({ + fields: ['name', 'status', 'due_date'], + filters: [ + ['status', '=', 'active'], + 'and', + ['priority', '>=', 3] + ], + sort: [['due_date', 'asc']], + skip: 0, + limit: 20 +}); +``` + +### 优化建议 + +#### ✅ 使用字段投影 + +**不好的做法:** +```typescript +// 返回所有字段(低效) +await app.object('user').find({ + filters: [['status', '=', 'active']] +}); +``` + +**好的做法:** +```typescript +// 仅返回需要的字段(高效) +await app.object('user').find({ + fields: ['id', 'name', 'email'], + filters: [['status', '=', 'active']] +}); +``` + +**影响:** 对于有很多字段的对象,减少 60-80% 的负载大小。 + +#### ✅ 在过滤器中使用索引字段 + +**不好的做法:** +```typescript +// 在非索引字段上过滤 +filters: [['description', 'contains', '紧急']] +``` + +**好的做法:** +```typescript +// 首先在索引字段上过滤 +filters: [ + ['status', '=', 'open'], // 有索引 + 'and', + ['priority', '=', 'high'] // 有索引 +] +``` + +**影响:** 根据数据集大小,可以提高 10-100 倍的查询速度。 + +#### ✅ 使用 expand 替代多次查询 + +**不好的做法:** +```typescript +// 多次往返 +const tasks = await app.object('task').find({}); +for (const task of tasks) { + task.project = await app.object('project').findOne(task.project_id); + task.assignee = await app.object('user').findOne(task.assignee_id); +} +``` + +**好的做法:** +```typescript +// 单次查询扩展(JOIN) +const tasks = await app.object('task').find({ + expand: { + project: { fields: ['name', 'status'] }, + assignee: { fields: ['name', 'email'] } + } +}); +``` + +**影响:** 通过消除 N+1 查询问题,减少 50-90% 的延迟。 + +--- + +## 2. REST API 接口 + +### 何时使用 + +✅ **最适合:** +- 简单的 CRUD 操作 +- 对查询需求有限的移动应用 +- 期望 REST 的第三方集成 +- 快速原型和 MVP +- 熟悉 REST 约定的开发者 + +### 基本用法 + +```bash +# 简单过滤列出记录 +GET /api/data/users?filters={"status":"active"}&limit=20 + +# 获取单条记录 +GET /api/data/users/user_123 + +# 创建记录 +POST /api/data/users +Content-Type: application/json + +{ + "name": "Alice", + "email": "alice@example.com" +} +``` + +### 优化建议 + +#### ✅ 选择需要的字段 + +```bash +# 不好:返回所有字段 +GET /api/data/users + +# 好:只选择需要的字段 +GET /api/data/users?fields=id,name,email +``` + +#### ✅ 利用 HTTP 缓存 + +```bash +# 为静态/读多的数据启用缓存头 +GET /api/data/products?status=active +Cache-Control: public, max-age=300 +``` + +**影响:** 可以消除读多端点 70-90% 的重复查询。 + +--- + +## 3. GraphQL 接口 + +### 何时使用 + +✅ **最适合:** +- 数据需求复杂的现代 SPA +- 在一个请求中获取多表数据 +- 实时应用程序(带订阅) +- 需要自省的开发工具 +- 带宽受限的移动应用 + +### 基本用法 + +```graphql +query GetTasksWithDetails { + taskList( + filters: { status: "active", priority: { gte: 3 } } + limit: 20 + sort: { due_date: ASC } + ) { + items { + id + name + status + project { + name + owner { + name + email + } + } + assignee { + name + avatar_url + } + } + } +} +``` + +### 优化建议 + +#### ✅ 只请求需要的字段 + +**不好的做法:** +```graphql +query { + userList { + items { + # 返回 20+ 个字段 + id name email phone address ... + } + } +} +``` + +**好的做法:** +```graphql +query { + userList { + items { + id + name + email + } + } +} +``` + +**影响:** 对于宽表,减少 70-90% 的负载大小。 + +#### ✅ 批量处理多个查询 + +**不好的做法:** +```javascript +const user = await graphql(`query { user(id: "123") { name } }`); +const tasks = await graphql(`query { taskList { items { name } } }`); +const projects = await graphql(`query { projectList { items { name } } }`); +``` + +**好的做法:** +```graphql +query GetDashboardData { + user(id: "123") { name email } + taskList(filters: { assignee_id: "123" }) { items { name status } } + projectList(filters: { owner_id: "123" }) { items { name progress } } +} +``` + +**影响:** 通过消除往返,减少 60-80% 的延迟。 + +--- + +## 4. 查询方式对比 + +### 场景 1:简单 CRUD 操作 + +**用例:** 创建新用户账户 + +**推荐:** REST API + +**原因:** 最简单的方法,标准约定,无开销。 + +--- + +### 场景 2:多数据源复杂仪表板 + +**用例:** 显示任务、项目和团队成员及关系的仪表板 + +**推荐:** GraphQL + +**原因:** 单次请求,精确字段选择,优雅处理嵌套数据。 + +--- + +### 场景 3:服务端业务逻辑 + +**用例:** 根据工作负载自动分配任务的自动化工作流 + +**推荐:** JSON-DSL + +**原因:** 类型安全,驱动不可知,程序化组合。 + +--- + +### 场景 4:AI 生成的查询 + +**用例:** LLM 从自然语言生成查询:"显示逾期的高优先级任务" + +**推荐:** JSON-DSL + +**原因:** 结构化格式防止幻觉,自动验证。 + +```typescript +// AI 生成(安全、已验证) +{ + "object": "tasks", + "ai_context": { + "intent": "查找逾期的高优先级任务", + "natural_language": "显示逾期的高优先级任务" + }, + "filters": [ + ["due_date", "<", "$today"], + "and", + ["priority", "=", "high"], + "and", + ["status", "!=", "completed"] + ], + "sort": [["due_date", "asc"]] +} +``` + +--- + +## 5. 高级优化技术 + +### 5.1 使用聚合进行分析 + +**不好的做法(应用层聚合):** +```typescript +const orders = await app.object('order').find({ + filters: [['status', '=', 'paid']] +}); + +// 慢:在应用代码中迭代 +let totalRevenue = 0; +for (const order of orders) { + totalRevenue += order.amount; +} +``` + +**好的做法(数据库层聚合):** +```typescript +const stats = await app.object('order').aggregate({ + filters: [['status', '=', 'paid']], + groupBy: ['customer_id'], + aggregate: [ + { func: 'sum', field: 'amount', alias: 'total_revenue' }, + { func: 'count', field: 'id', alias: 'order_count' } + ] +}); +``` + +**影响:** 对于大数据集,快 100-1000 倍。 + +--- + +### 5.2 使用 DISTINCT 获取唯一值 + +**不好的做法:** +```typescript +const orders = await app.object('order').find({ + fields: ['customer_id'] +}); +const uniqueCustomers = [...new Set(orders.map(o => o.customer_id))]; +``` + +**好的做法:** +```typescript +const uniqueCustomers = await app.object('order').distinct('customer_id', { + filters: [['year', '=', 2024]] +}); +``` + +**影响:** 对于高重复字段,减少 90%+ 的数据传输。 + +--- + +### 5.3 使用合适的索引 + +```yaml +# task.object.yml +name: task +fields: + status: + type: select + options: [open, in_progress, completed] + assignee_id: + type: lookup + reference_to: users + due_date: + type: date + +indexes: + # 常见查询的复合索引 + - fields: [status, assignee_id, due_date] + name: idx_task_active_query + + # 排序索引 + - fields: [created_at] + name: idx_task_created +``` + +**影响:** 带索引过滤器的查询快 10-100 倍。 + +--- + +### 5.4 尽可能避免 OR 过滤器 + +**不好的做法(OR 需要多次索引扫描):** +```typescript +filters: [ + ['status', '=', 'pending'], + 'or', + ['status', '=', 'active'] +] +``` + +**好的做法(IN 使用单次索引扫描):** +```typescript +filters: [ + ['status', 'in', ['pending', 'active']] +] +``` + +**影响:** 对于大表,快 2-5 倍。 + +--- + +## 6. 性能最佳实践总结 + +| 实践 | 影响 | 难度 | +|------|------|------| +| 使用字段投影 | 高 | 简单 | +| 为过滤/排序字段添加索引 | 非常高 | 中等 | +| 使用聚合进行分析 | 非常高 | 简单 | +| 用 expand 消除 N+1 查询 | 非常高 | 简单 | +| 实现分页 | 高 | 简单 | +| 为大集合使用基于游标的分页 | 高 | 中等 | +| 使用 `in` 操作符替代多个 `or` | 中等 | 简单 | +| 在 GraphQL 中批量查询 | 高 | 简单 | +| 使用 `distinct` 获取唯一值 | 高 | 简单 | +| 为 REST 启用 HTTP 缓存 | 高 | 中等 | + +--- + +## 7. 选择正确方法:决策树 + +``` +开始 +│ +├─ 这是服务端逻辑还是 AI 生成的? +│ └─ 是 → 使用 JSON-DSL ✅ +│ +├─ 需要在一个请求中获取复杂的嵌套数据吗? +│ └─ 是 → 使用 GraphQL ✅ +│ +├─ 这是简单的 CRUD 操作吗? +│ └─ 是 → 使用 REST ✅ +│ +└─ 需要最大的灵活性? + └─ 使用 JSON-DSL ✅(最通用) +``` + +--- + +## 8. 结论 + +**关键要点:** + +1. **JSON-DSL** 是通用核心 - 用于服务端逻辑、AI 集成和跨驱动兼容性。 + +2. **GraphQL** 在具有嵌套关系的复杂数据需求方面表现出色,非常适合现代前端。 + +3. **REST** 非常适合简单的 CRUD 操作和第三方集成。 + +4. **优化比接口更重要** - 无论使用哪种方法,都要专注于索引、字段投影和分页。 + +5. **可以混合方法** - 前端仪表板使用 GraphQL,后端工作流使用 JSON-DSL。 + +**推荐的默认技术栈:** +- **服务端:** JSON-DSL(类型安全,驱动不可知) +- **客户端(复杂):** GraphQL(高效,灵活) +- **客户端(简单):** REST(快速,熟悉) +- **AI 集成:** JSON-DSL(防幻觉) + +--- + +## 9. 延伸阅读 + +- [查询语言规范](../spec/query-language.md) - 完整的 JSON-DSL 参考 +- [查询指南](./querying.md) - 逐步查询示例 +- [GraphQL API 文档](../api/graphql.md) - GraphQL 设置和使用 +- [REST API 文档](../api/rest.md) - REST 端点参考 +- [完整英文版指南](./query-syntax-alternatives.md) - 详细的英文版本 + +--- + +**需要帮助?** + +- 📖 [文档](https://objectql.org/docs) +- 💬 [社区 Discord](https://discord.gg/objectql) +- 🐛 [报告问题](https://github.com/objectstack-ai/objectql/issues) From 812b9f36f715be7a0fadced9748d4bcdbcfa5fa2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 03:59:23 +0000 Subject: [PATCH 4/5] Rename to query best practices and remove Chinese version Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- README.md | 2 +- docs/.vitepress/config.mts | 2 +- docs/api/index.md | 4 +- ...lternatives.md => query-best-practices.md} | 4 +- docs/guide/query-syntax-alternatives-zh.md | 510 ------------------ docs/guide/querying.md | 4 +- docs/spec/query-language.md | 4 +- 7 files changed, 10 insertions(+), 520 deletions(-) rename docs/guide/{query-syntax-alternatives.md => query-best-practices.md} (98%) delete mode 100644 docs/guide/query-syntax-alternatives-zh.md diff --git a/README.md b/README.md index 3d8ac718..c1bd3dda 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ ObjectQL supports three distinct query interfaces, each optimized for different * **REST API:** Simple CRUD operations, mobile apps, third-party integrations * **GraphQL:** Complex data graphs, modern SPAs, efficient multi-table fetching -**Not sure which to use?** Check out the [Query Syntax Alternatives & Optimization Guide](./docs/guide/query-syntax-alternatives.md) for detailed comparisons, performance strategies, and decision frameworks. +**Looking for best practices?** Check out the [Query Best Practices Guide](./docs/guide/query-best-practices.md) for detailed comparisons, performance strategies, and optimization techniques. --- diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 69d5edd2..65e4690b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -26,7 +26,7 @@ const guideSidebar = [ { text: 'Data Modeling', link: '/guide/data-modeling' }, { text: 'Unified ID Migration', link: '/guide/migration-id-field' }, { text: 'Querying Data', link: '/guide/querying' }, - { text: 'Query Syntax Alternatives', link: '/guide/query-syntax-alternatives' }, + { text: 'Query Best Practices', link: '/guide/query-best-practices' }, { text: 'Formulas & Rules Syntax', link: '/guide/formulas-and-rules' }, { text: '↳ Quick Reference', link: '/guide/formulas-and-rules-quick-ref' }, { text: 'Business Logic (Hooks)', link: '/guide/logic-hooks' }, diff --git a/docs/api/index.md b/docs/api/index.md index 3df72fd7..f5c10d72 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -29,8 +29,8 @@ ObjectQL uses a **unified `id` field** as the primary key across all database dr | **GraphQL** | Modern frontends with complex data requirements | `POST /api/graphql` | [Read Guide](./graphql.md) | | **Metadata** | Admin interfaces, schema discovery | `/api/metadata/*` | [Read Guide](./metadata.md) | -> **🚀 Choosing the right API style?** -> Check out the [Query Syntax Alternatives & Optimization Guide](../guide/query-syntax-alternatives.md) for a detailed comparison, performance benchmarks, and decision frameworks to help you pick the best approach for your use case. +> **🚀 Want to optimize your queries?** +> Check out the [Query Best Practices Guide](../guide/query-best-practices.md) for performance optimization strategies, detailed comparisons, and recommendations to help you choose the best approach for your use case. ## Quick Links diff --git a/docs/guide/query-syntax-alternatives.md b/docs/guide/query-best-practices.md similarity index 98% rename from docs/guide/query-syntax-alternatives.md rename to docs/guide/query-best-practices.md index eeef6aa7..88e82f7e 100644 --- a/docs/guide/query-syntax-alternatives.md +++ b/docs/guide/query-best-practices.md @@ -1,6 +1,6 @@ -# Query Syntax Alternatives & Optimization Guide +# Query Best Practices -This guide evaluates the current ObjectQL query approaches and provides recommendations for different use cases, along with optimization strategies to maximize performance and developer experience. +This guide provides best practices, performance optimization strategies, and recommendations for querying data in ObjectQL across different query interfaces (JSON-DSL, REST, GraphQL). --- diff --git a/docs/guide/query-syntax-alternatives-zh.md b/docs/guide/query-syntax-alternatives-zh.md deleted file mode 100644 index da66b5ad..00000000 --- a/docs/guide/query-syntax-alternatives-zh.md +++ /dev/null @@ -1,510 +0,0 @@ -# 查询语法替代方案与优化指南 - -本指南评估当前 ObjectQL 的查询方法,并为不同用例提供建议,以及最大化性能和开发体验的优化策略。 - -> **📖 完整英文版:** [Query Syntax Alternatives & Optimization Guide](./query-syntax-alternatives.md) - ---- - -## 执行摘要 - -### 当前查询语法概览 - -ObjectQL 提供 **三种不同的查询接口**,每种都针对不同场景优化: - -| 方式 | 最适合 | 复杂度 | 性能 | AI 友好度 | -|------|--------|--------|------|----------| -| **JSON-DSL(核心)** | 服务端逻辑、AI 代理 | 中等 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | -| **REST API** | 简单 CRUD、移动应用 | 低 | ⭐⭐⭐⭐ | ⭐⭐⭐ | -| **GraphQL** | 复杂数据图、现代 SPA | 高 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | - ---- - -## 1. JSON-DSL 协议(推荐默认方式) - -### 什么是 JSON-DSL - -ObjectQL 的核心查询语言 - 一种结构化的 JSON 表示,作为数据操作的抽象语法树(AST)。 - -### 何时使用 - -✅ **最适合:** -- 服务端业务逻辑和钩子 -- AI 生成的查询(防止幻觉) -- 跨驱动兼容性(SQL、MongoDB、远程) -- 复杂的嵌套逻辑过滤 -- 程序化查询构造 - -### 基本语法 - -```typescript -const tasks = await app.object('task').find({ - fields: ['name', 'status', 'due_date'], - filters: [ - ['status', '=', 'active'], - 'and', - ['priority', '>=', 3] - ], - sort: [['due_date', 'asc']], - skip: 0, - limit: 20 -}); -``` - -### 优化建议 - -#### ✅ 使用字段投影 - -**不好的做法:** -```typescript -// 返回所有字段(低效) -await app.object('user').find({ - filters: [['status', '=', 'active']] -}); -``` - -**好的做法:** -```typescript -// 仅返回需要的字段(高效) -await app.object('user').find({ - fields: ['id', 'name', 'email'], - filters: [['status', '=', 'active']] -}); -``` - -**影响:** 对于有很多字段的对象,减少 60-80% 的负载大小。 - -#### ✅ 在过滤器中使用索引字段 - -**不好的做法:** -```typescript -// 在非索引字段上过滤 -filters: [['description', 'contains', '紧急']] -``` - -**好的做法:** -```typescript -// 首先在索引字段上过滤 -filters: [ - ['status', '=', 'open'], // 有索引 - 'and', - ['priority', '=', 'high'] // 有索引 -] -``` - -**影响:** 根据数据集大小,可以提高 10-100 倍的查询速度。 - -#### ✅ 使用 expand 替代多次查询 - -**不好的做法:** -```typescript -// 多次往返 -const tasks = await app.object('task').find({}); -for (const task of tasks) { - task.project = await app.object('project').findOne(task.project_id); - task.assignee = await app.object('user').findOne(task.assignee_id); -} -``` - -**好的做法:** -```typescript -// 单次查询扩展(JOIN) -const tasks = await app.object('task').find({ - expand: { - project: { fields: ['name', 'status'] }, - assignee: { fields: ['name', 'email'] } - } -}); -``` - -**影响:** 通过消除 N+1 查询问题,减少 50-90% 的延迟。 - ---- - -## 2. REST API 接口 - -### 何时使用 - -✅ **最适合:** -- 简单的 CRUD 操作 -- 对查询需求有限的移动应用 -- 期望 REST 的第三方集成 -- 快速原型和 MVP -- 熟悉 REST 约定的开发者 - -### 基本用法 - -```bash -# 简单过滤列出记录 -GET /api/data/users?filters={"status":"active"}&limit=20 - -# 获取单条记录 -GET /api/data/users/user_123 - -# 创建记录 -POST /api/data/users -Content-Type: application/json - -{ - "name": "Alice", - "email": "alice@example.com" -} -``` - -### 优化建议 - -#### ✅ 选择需要的字段 - -```bash -# 不好:返回所有字段 -GET /api/data/users - -# 好:只选择需要的字段 -GET /api/data/users?fields=id,name,email -``` - -#### ✅ 利用 HTTP 缓存 - -```bash -# 为静态/读多的数据启用缓存头 -GET /api/data/products?status=active -Cache-Control: public, max-age=300 -``` - -**影响:** 可以消除读多端点 70-90% 的重复查询。 - ---- - -## 3. GraphQL 接口 - -### 何时使用 - -✅ **最适合:** -- 数据需求复杂的现代 SPA -- 在一个请求中获取多表数据 -- 实时应用程序(带订阅) -- 需要自省的开发工具 -- 带宽受限的移动应用 - -### 基本用法 - -```graphql -query GetTasksWithDetails { - taskList( - filters: { status: "active", priority: { gte: 3 } } - limit: 20 - sort: { due_date: ASC } - ) { - items { - id - name - status - project { - name - owner { - name - email - } - } - assignee { - name - avatar_url - } - } - } -} -``` - -### 优化建议 - -#### ✅ 只请求需要的字段 - -**不好的做法:** -```graphql -query { - userList { - items { - # 返回 20+ 个字段 - id name email phone address ... - } - } -} -``` - -**好的做法:** -```graphql -query { - userList { - items { - id - name - email - } - } -} -``` - -**影响:** 对于宽表,减少 70-90% 的负载大小。 - -#### ✅ 批量处理多个查询 - -**不好的做法:** -```javascript -const user = await graphql(`query { user(id: "123") { name } }`); -const tasks = await graphql(`query { taskList { items { name } } }`); -const projects = await graphql(`query { projectList { items { name } } }`); -``` - -**好的做法:** -```graphql -query GetDashboardData { - user(id: "123") { name email } - taskList(filters: { assignee_id: "123" }) { items { name status } } - projectList(filters: { owner_id: "123" }) { items { name progress } } -} -``` - -**影响:** 通过消除往返,减少 60-80% 的延迟。 - ---- - -## 4. 查询方式对比 - -### 场景 1:简单 CRUD 操作 - -**用例:** 创建新用户账户 - -**推荐:** REST API - -**原因:** 最简单的方法,标准约定,无开销。 - ---- - -### 场景 2:多数据源复杂仪表板 - -**用例:** 显示任务、项目和团队成员及关系的仪表板 - -**推荐:** GraphQL - -**原因:** 单次请求,精确字段选择,优雅处理嵌套数据。 - ---- - -### 场景 3:服务端业务逻辑 - -**用例:** 根据工作负载自动分配任务的自动化工作流 - -**推荐:** JSON-DSL - -**原因:** 类型安全,驱动不可知,程序化组合。 - ---- - -### 场景 4:AI 生成的查询 - -**用例:** LLM 从自然语言生成查询:"显示逾期的高优先级任务" - -**推荐:** JSON-DSL - -**原因:** 结构化格式防止幻觉,自动验证。 - -```typescript -// AI 生成(安全、已验证) -{ - "object": "tasks", - "ai_context": { - "intent": "查找逾期的高优先级任务", - "natural_language": "显示逾期的高优先级任务" - }, - "filters": [ - ["due_date", "<", "$today"], - "and", - ["priority", "=", "high"], - "and", - ["status", "!=", "completed"] - ], - "sort": [["due_date", "asc"]] -} -``` - ---- - -## 5. 高级优化技术 - -### 5.1 使用聚合进行分析 - -**不好的做法(应用层聚合):** -```typescript -const orders = await app.object('order').find({ - filters: [['status', '=', 'paid']] -}); - -// 慢:在应用代码中迭代 -let totalRevenue = 0; -for (const order of orders) { - totalRevenue += order.amount; -} -``` - -**好的做法(数据库层聚合):** -```typescript -const stats = await app.object('order').aggregate({ - filters: [['status', '=', 'paid']], - groupBy: ['customer_id'], - aggregate: [ - { func: 'sum', field: 'amount', alias: 'total_revenue' }, - { func: 'count', field: 'id', alias: 'order_count' } - ] -}); -``` - -**影响:** 对于大数据集,快 100-1000 倍。 - ---- - -### 5.2 使用 DISTINCT 获取唯一值 - -**不好的做法:** -```typescript -const orders = await app.object('order').find({ - fields: ['customer_id'] -}); -const uniqueCustomers = [...new Set(orders.map(o => o.customer_id))]; -``` - -**好的做法:** -```typescript -const uniqueCustomers = await app.object('order').distinct('customer_id', { - filters: [['year', '=', 2024]] -}); -``` - -**影响:** 对于高重复字段,减少 90%+ 的数据传输。 - ---- - -### 5.3 使用合适的索引 - -```yaml -# task.object.yml -name: task -fields: - status: - type: select - options: [open, in_progress, completed] - assignee_id: - type: lookup - reference_to: users - due_date: - type: date - -indexes: - # 常见查询的复合索引 - - fields: [status, assignee_id, due_date] - name: idx_task_active_query - - # 排序索引 - - fields: [created_at] - name: idx_task_created -``` - -**影响:** 带索引过滤器的查询快 10-100 倍。 - ---- - -### 5.4 尽可能避免 OR 过滤器 - -**不好的做法(OR 需要多次索引扫描):** -```typescript -filters: [ - ['status', '=', 'pending'], - 'or', - ['status', '=', 'active'] -] -``` - -**好的做法(IN 使用单次索引扫描):** -```typescript -filters: [ - ['status', 'in', ['pending', 'active']] -] -``` - -**影响:** 对于大表,快 2-5 倍。 - ---- - -## 6. 性能最佳实践总结 - -| 实践 | 影响 | 难度 | -|------|------|------| -| 使用字段投影 | 高 | 简单 | -| 为过滤/排序字段添加索引 | 非常高 | 中等 | -| 使用聚合进行分析 | 非常高 | 简单 | -| 用 expand 消除 N+1 查询 | 非常高 | 简单 | -| 实现分页 | 高 | 简单 | -| 为大集合使用基于游标的分页 | 高 | 中等 | -| 使用 `in` 操作符替代多个 `or` | 中等 | 简单 | -| 在 GraphQL 中批量查询 | 高 | 简单 | -| 使用 `distinct` 获取唯一值 | 高 | 简单 | -| 为 REST 启用 HTTP 缓存 | 高 | 中等 | - ---- - -## 7. 选择正确方法:决策树 - -``` -开始 -│ -├─ 这是服务端逻辑还是 AI 生成的? -│ └─ 是 → 使用 JSON-DSL ✅ -│ -├─ 需要在一个请求中获取复杂的嵌套数据吗? -│ └─ 是 → 使用 GraphQL ✅ -│ -├─ 这是简单的 CRUD 操作吗? -│ └─ 是 → 使用 REST ✅ -│ -└─ 需要最大的灵活性? - └─ 使用 JSON-DSL ✅(最通用) -``` - ---- - -## 8. 结论 - -**关键要点:** - -1. **JSON-DSL** 是通用核心 - 用于服务端逻辑、AI 集成和跨驱动兼容性。 - -2. **GraphQL** 在具有嵌套关系的复杂数据需求方面表现出色,非常适合现代前端。 - -3. **REST** 非常适合简单的 CRUD 操作和第三方集成。 - -4. **优化比接口更重要** - 无论使用哪种方法,都要专注于索引、字段投影和分页。 - -5. **可以混合方法** - 前端仪表板使用 GraphQL,后端工作流使用 JSON-DSL。 - -**推荐的默认技术栈:** -- **服务端:** JSON-DSL(类型安全,驱动不可知) -- **客户端(复杂):** GraphQL(高效,灵活) -- **客户端(简单):** REST(快速,熟悉) -- **AI 集成:** JSON-DSL(防幻觉) - ---- - -## 9. 延伸阅读 - -- [查询语言规范](../spec/query-language.md) - 完整的 JSON-DSL 参考 -- [查询指南](./querying.md) - 逐步查询示例 -- [GraphQL API 文档](../api/graphql.md) - GraphQL 设置和使用 -- [REST API 文档](../api/rest.md) - REST 端点参考 -- [完整英文版指南](./query-syntax-alternatives.md) - 详细的英文版本 - ---- - -**需要帮助?** - -- 📖 [文档](https://objectql.org/docs) -- 💬 [社区 Discord](https://discord.gg/objectql) -- 🐛 [报告问题](https://github.com/objectstack-ai/objectql/issues) diff --git a/docs/guide/querying.md b/docs/guide/querying.md index 57f5f10f..d55a9b86 100644 --- a/docs/guide/querying.md +++ b/docs/guide/querying.md @@ -4,8 +4,8 @@ ObjectQL uses a **JSON-based Protocol** for all data operations. Unlike SQL (str This design makes it strictly typed, easy to serialise/transport over HTTP, and safe from injection—perfect for both human developers and AI Agents. -> **📖 Looking for query alternatives and optimization strategies?** -> Check out the [Query Syntax Alternatives & Optimization Guide](./query-syntax-alternatives.md) for a comprehensive comparison of JSON-DSL, REST, and GraphQL approaches, including performance best practices. +> **📖 Looking for query best practices and optimization strategies?** +> Check out the [Query Best Practices Guide](./query-best-practices.md) for a comprehensive guide on choosing the right approach (JSON-DSL, REST, GraphQL) and performance optimization techniques. ## The `find()` Operation diff --git a/docs/spec/query-language.md b/docs/spec/query-language.md index 07dfebc9..6cbfde5e 100644 --- a/docs/spec/query-language.md +++ b/docs/spec/query-language.md @@ -14,8 +14,8 @@ It is designed to be: 3. **AI-Friendly:** The rigid structure prevents hallucination when generating queries via LLMs. 4. **Intent-Driven:** Queries can include natural language intent for explainability. -> **💡 Need help choosing between JSON-DSL, REST, or GraphQL?** -> See the [Query Syntax Alternatives & Optimization Guide](../guide/query-syntax-alternatives.md) for recommendations, performance tips, and decision frameworks. +> **💡 Looking for performance tips and best practices?** +> See the [Query Best Practices Guide](../guide/query-best-practices.md) for optimization strategies, performance benchmarks, and recommendations for choosing between JSON-DSL, REST, and GraphQL. ### 1.1 AI-Enhanced Queries (Optional) From 619ab629fd05489d0204054dac282f489cdc1b99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 04:08:04 +0000 Subject: [PATCH 5/5] Address code review feedback: fix mutations, add legend, clarify examples Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- docs/guide/query-best-practices.md | 35 ++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/guide/query-best-practices.md b/docs/guide/query-best-practices.md index 88e82f7e..22ef87f7 100644 --- a/docs/guide/query-best-practices.md +++ b/docs/guide/query-best-practices.md @@ -14,6 +14,8 @@ ObjectQL provides **three distinct query interfaces**, each optimized for differ | **REST API** | Simple CRUD, mobile apps | Low | ⭐⭐⭐⭐ | ⭐⭐⭐ | | **GraphQL** | Complex data graphs, modern SPAs | High | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | +*Rating scale: ⭐ = lowest, ⭐⭐⭐⭐⭐ = highest* + --- ## 2. JSON-DSL Query Protocol (Recommended Default) @@ -117,11 +119,17 @@ await app.object('order').find({ **Bad:** ```typescript -// Multiple round trips +// Multiple round trips (N+1 query problem) const tasks = await app.object('task').find({}); +const enrichedTasks = []; for (const task of tasks) { - task.project = await app.object('project').findOne(task.project_id); - task.assignee = await app.object('user').findOne(task.assignee_id); + const project = await app.object('project').findOne(task.project_id); + const assignee = await app.object('user').findOne(task.assignee_id); + enrichedTasks.push({ + ...task, + project, + assignee + }); } ``` @@ -408,11 +416,14 @@ query GetDashboardData { When building custom resolvers, use DataLoader pattern to batch database queries: ```typescript -// Bad: N+1 queries +// Bad: N+1 queries (inefficient) const tasks = await taskRepo.find(); -for (const task of tasks) { - task.assignee = await userRepo.findOne(task.assignee_id); -} +const tasksWithAssignee = await Promise.all( + tasks.map(async (task) => ({ + ...task, + assignee: await userRepo.findOne(task.assignee_id), + })), +); // Good: Batched loading (1+1 queries) const tasks = await taskRepo.find(); @@ -421,9 +432,10 @@ const users = await userRepo.find({ filters: [['id', 'in', userIds]] }); const userMap = new Map(users.map(u => [u.id, u])); -tasks.forEach(task => { - task.assignee = userMap.get(task.assignee_id); -}); +const tasksWithAssigneeBatched = tasks.map((task) => ({ + ...task, + assignee: userMap.get(task.assignee_id), +})); ``` --- @@ -549,6 +561,8 @@ async function autoAssign(task: any) { ``` **Why NOT SQL strings:** + +*Example of AI hallucination:* ```sql -- AI might hallucinate invalid syntax SELECT * FROM tasks WHERE due_date < NOW() @@ -801,7 +815,6 @@ query { - [Querying Guide](./querying.md) - Step-by-step query examples - [GraphQL API Documentation](../api/graphql.md) - GraphQL setup and usage - [REST API Documentation](../api/rest.md) - REST endpoint reference -- [Performance Tuning](./performance.md) - Advanced optimization strategies ---