{"id":26976111,"url":"https://github.com/alexandrughinea/sqlx-paginated","last_synced_at":"2025-10-16T22:28:14.723Z","repository":{"id":262299640,"uuid":"882091287","full_name":"alexandrughinea/sqlx-paginated","owner":"alexandrughinea","description":"A flexible, type-safe SQLx query builder for dynamic web APIs, offering seamless pagination, searching, filtering, and sorting.","archived":false,"fork":false,"pushed_at":"2025-02-20T08:46:59.000Z","size":190,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-28T05:45:55.894Z","etag":null,"topics":["rust","sqlx"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexandrughinea.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-01T21:38:34.000Z","updated_at":"2025-03-18T16:00:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"c78fbccf-75a3-4197-a0ea-44dca2d50fda","html_url":"https://github.com/alexandrughinea/sqlx-paginated","commit_stats":null,"previous_names":["alexandrughinea/sqlx-paginated"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandrughinea%2Fsqlx-paginated","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandrughinea%2Fsqlx-paginated/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandrughinea%2Fsqlx-paginated/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandrughinea%2Fsqlx-paginated/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexandrughinea","download_url":"https://codeload.github.com/alexandrughinea/sqlx-paginated/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246995668,"owners_count":20866449,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rust","sqlx"],"created_at":"2025-04-03T11:35:47.753Z","updated_at":"2025-10-16T22:28:09.662Z","avatar_url":"https://github.com/alexandrughinea.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sqlx-paginated\n\n[![Rust](https://github.com/alexandrughinea/sqlx-paginated/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/alexandrughinea/sqlx-paginated/actions/workflows/rust.yml)\n[![crates.io](https://img.shields.io/crates/v/sqlx-paginated.svg)](https://crates.io/crates/sqlx-paginated)\n[![docs](https://docs.rs/sqlx-paginated/badge.svg)](https://docs.rs/sqlx-paginated/latest/sqlx_paginated/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA blazingly fast, type-safe, fluid query builder for dynamic APIs, offering seamless pagination, sorting and dynamic filtering on top of [SQLx](https://docs.rs/sqlx/latest/sqlx).\n\n## Table of Contents\n- [Features](#features)\n  - [Core Capabilities](#core-capabilities)\n  - [Technical Features](#technical-features)\n  - [Query Features](#query-features)\n- [Database Support](#database-support)\n  - [Current vs Planned Support](#current-vs-planned-support)\n- [Market Analysis](#market-analysis)\n  - [Ecosystem Gaps](#ecosystem-gaps)\n  - [Unique Selling Points](#unique-selling-points)\n  - [Target Audience](#target-audience)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n  - [Basic Usage](#basic-usage)\n  - [Response Example](#response-example)\n- [API Reference](#api-reference)\n  - [Pagination Parameters](#pagination-parameters)\n  - [Sort Parameters](#sort-parameters)\n  - [Search Parameters](#search-parameters)\n  - [Date Range Parameters](#date-range-parameters)\n  - [Filtering Parameters](#filtering-parameters)\n- [Query Examples](#query-examples)\n  - [Combined search, sort, date range, pagination and filter](#combined-search-sort-date-range-pagination-and-custom-filter)\n  - [Date Range combined with two other filters](#date-range-filter-combined-with-two-other-custom-filters)\n- [Performance Considerations](#performance-considerations)\n  - [Query Pattern Optimization](#query-pattern-optimization)\n  - [Recommended Indexes](#recommended-indexes)\n  - [Pagination Performance](#pagination-performance)\n- [Security Features](#security-features)\n  - [Input Sanitization](#input-sanitization)\n  - [Protected Patterns](#protected-patterns)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n### Core Capabilities\n- 🔍 Full-text search with column specification\n- 📑 Smart pagination with customizable page size\n- 🔄 Dynamic sorting on any column\n- 🎯 Flexible filtering system \n- 📅 Date range filtering\n- 🔒 Type-safe operations\n- 🔥 High performance\n- 🛡️ SQL injection protection\n\n### Technical Features\n- Builder patterns for query parameters and query construction\n- Graceful error handling\n- Logging with tracing (if enabled)\n- Macro and function syntax support\n\n### Query Features\n- Case-insensitive search\n- Multiple column search\n- Complex filtering conditions\n- Date-based filtering\n- Dynamic sort direction\n- Customizable page size\n- Result count optimization (opt-out of total records lookup ahead)\n\n## Database Support\n\n### Current vs Planned Support\n| Database    | Status      | Version | Features                           | Notes                                       |\n|-------------|-------------|---------|-----------------------------------|---------------------------------------------|\n| PostgreSQL  | ✅ Supported | 12+     | All features supported            | Ready                                       |\n| SQLite      | 🚧 Planned  | 3.35+   | Basic features planned           | On roadmap, development starting in Q2 2025 |\n| MySQL       | 🚧 Planned  | 8.0+    | Core features planned            | On roadmap, development starting in Q3 2025 |\n\n⚠️ Note: `This documentation covers PostgreSQL features only, as it's currently the only supported database.`\n\n## Market Analysis\n\n### Ecosystem Gaps\n1. **Query builders**\n   - Diesel: Full ORM, can be heavyweight\n   - SeaQuery: Generic and can be verbose\n   - sqlbuilder: Basic SQL building without pagination or security\n\n2. **Missing features in existing solutions**\n   - Easy integration with web frameworks\n   - Automatic type casting\n   - Typesafe search/filter/sort/pagination capabilities\n\n### Unique Selling Points\n\n1. **Quick Web Framework Integration with minimal footprint**\n\n[Actix Web](https://actix.rs/) handler example\n```rust\nuse sqlx_paginated::{paginated_query_as, FlatQueryParams};\nuse actix_web::{web, Responder, HttpResponse};\n\nasync fn list_users(web::Query(params): web::Query\u003cFlatQueryParams\u003e) -\u003e impl Responder {\n    let paginated_users = paginated_query_as!(User, \"SELECT * FROM users\")\n        .with_params(params)\n        .fetch_paginated(\u0026pool)\n        .await\n        .unwrap();\n    \n    HttpResponse::Ok().json(json!(paginated_users))\n}\n```\n\n2. **Type Safety \u0026 Ergonomics for parameter configuration**\n```rust\nlet params = QueryParamsBuilder::\u003cUser\u003e::new()\n    .with_pagination(1, 10)\n    .with_sort(\"created_at\", QuerySortDirection::Descending)\n    .with_search(\"john\", vec![\"name\", \"email\"])\n    .build();\n```\n\n3. **Advanced Builder Patterns**\n- Optional fluent API for query parameters (QueryParams) which allow defining search, search location, date filtering, ordering, and custom filtering.\n- Fluent API for the entire supported feature set, more here: [advanced example](src/paginated_query_as/examples/paginated_query_builder_advanced_examples.rs)\n\n```rust\n    paginated_query_as!(UserExample, \"SELECT * FROM users\")\n        .with_params(initial_params)\n        .with_query_builder(|params| {\n            // Can override the default query builder (build_query_with_safe_defaults) with a complete custom one:\n            QueryBuilder::\u003cUserExample, Postgres\u003e::new()\n                .with_search(params) // Add or remove search feature from the query;\n                .with_filters(params) // Add or remove custom filters from the query;\n                .with_date_range(params) // Add or remove data range;\n                .with_raw_condition(\"\") // Add raw condition, no checks.\n                .disable_protection() // This removes all column safety checks.\n                .with_combined_conditions(|builder| {\n                   // ...\n                .build()\n        })\n        .disable_totals_count() // Disables the calculation of total record count\n        .fetch_paginated(\u0026pool)\n        .await\n        .unwrap()\n```\n\n\n### Target Audience\n1. **Primary users**\n   - Rust web developers or API teams\n   - Teams needing quick and secure query building\n   - Projects requiring pagination and dynamic filtering APIs\n   - SQLx users wanting higher-level abstractions for repetitive tasks\n\n2. **Use cases**\n   - REST APIs with pagination\n   - Admin panels\n   - Data exploration interfaces\n\n## Installation\n\nAdd to `Cargo.toml`:\n```toml\n[dependencies]\nsqlx_paginated = { version = \"0.2.29\", features = [\"postgres\"] }\n```\n\n## Quick Start\n\n### Basic Usage\n```rust\n#[derive(sqlx::FromRow, serde::Serialize)]\nstruct User {\n    id: i64,\n    first_name: String,\n    last_name: String,\n    email: String,\n    confirmed: bool,\n    created_at: Option\u003cDateTime\u003cUtc\u003e\u003e,\n}\n\n/// Macro usage example\nasync fn get_users(pool: \u0026PgPool) -\u003e Result\u003cPaginatedResponse\u003cUser\u003e, sqlx::Error\u003e {\n    let params = QueryParamsBuilder::\u003cUser\u003e::new()\n        .with_pagination(1, 10)\n        .with_sort(\"created_at\", QuerySortDirection::Descending)\n        .with_search(\"replace with dynamic value\", vec![\"first_name\", \"last_name\", \"email\"])\n        .build();\n    let paginated_response = paginated_query_as!(User, \"SELECT * FROM users\")\n        // Alternative function call example (if macros don't fit your use case):\n        // paginated_query_as::\u003cUser\u003e(\"SELECT * FROM users\")\n        .with_params(params)\n        .fetch_paginated(pool)\n        .await?;\n\n    Ok(paginated_response)\n}\n```\n\n### Response Example\n```json\n{\n  \"records\": [\n    {\n      \"id\": \"409e3900-c190-4dad-882d-ec2d40245329\",\n      \"first_name\": \"John\",\n      \"last_name\": \"Smith\",\n      \"email\": \"john@example.com\",\n      \"confirmed\": true,\n      \"created_at\": \"2024-01-01T00:00:00Z\"\n    }\n  ],\n  \"page\": 1,\n  \"page_size\": 10,\n  \"total_pages\": 1\n}\n```\n\n## API Reference\n\n### Pagination Parameters\n| Parameter  | Type    | Default | Min | Max | Description                    |\n|------------|---------|---------|-----|-----|--------------------------------|\n| page       | integer | 1       | 1   | n/a | Current page number            |\n| page_size  | integer | 10      | 10  | 50  | Number of records per page     |\n\n#### Example:\n```\nGET /v1/internal/users?page=2\u0026page_size=20\n```\n\n### Sort Parameters\n| Parameter      | Type   | Default    | Allowed Values              | Description                |\n|----------------|--------|------------|----------------------------|----------------------------|\n| sort_column    | string | created_at | Any valid table column     | Column name to sort by     |\n| sort_direction | string | descending | ascending, descending      | Sort direction             |\n\n#### Example:\n```\nGET /v1/internal/users?sort_column=last_name\u0026sort_direction=ascending\n```\n\n### Search Parameters\n| Parameter      | Type   | Default           | Max Length | Description                          |\n|----------------|--------|-------------------|------------|--------------------------------------|\n| search         | string | null             | 100        | Search term to filter results         |\n| search_columns | string | name,description | n/a        | Comma-separated list of columns       |\n\n#### Example:\n```\nGET /v1/internal/users?search=john\u0026search_columns=first_name,last_name,email\n```\n\n### Date Range Parameters\n| Parameter    | Type     | Default    | Format    | Description           |\n|-------------|----------|------------|-----------|----------------------|\n| date_column | string   | created_at | Column name| Column to filter on   |\n| date_after  | datetime | null       | ISO 8601  | Start of date range   |\n| date_before | datetime | null       | ISO 8601  | End of date range     |\n\n#### Example:\n```\nGET /v1/internal/users?date_column=created_at\u0026date_after=2024-01-01T00:00:00Z\n```\n\n### Filtering Parameters\n| Parameter | Type                    | Default           | Max Length | Description                             |\n|-----------|-------------------------|-------------------|------------|-----------------------------------------|\n| *         | string,boolean,datetime | null             | 100        | Any valid table column for given struct |\n\n#### Example:\n```\nGET /v1/internal/users?confirmed=true\n```\n\n## Query Examples\n\n- Given the following `struct`, we can then perform search and filtering\nagainst its own fields. \n- We should also receive a paginated response back with the matching records.\n\n```rust\n#[derive(Serialize, Deserialize, FromRow, Default)]\npub struct User {\n    pub id: Option\u003cUuid\u003e,\n    pub first_name: String,\n    pub last_name: String,\n    pub confirmed: Option\u003cbool\u003e,\n    pub created_at: Option\u003cDateTime\u003cUtc\u003e\u003e,\n    pub updated_at: Option\u003cDateTime\u003cUtc\u003e\u003e,\n}\n```\n\n1. ### Combined search, sort, date range, pagination and custom filter\n\n- Notice the `confirmed=true` filter.\n\n#### Request:\n```\nGET /v1/internal/users\n    ?search=john\n    \u0026search_columns=first_name,last_name,email\n    \u0026sort_column=created_at\n    \u0026sort_direction=descending\n    \u0026date_before=2024-11-03T12:30:12.081598Z\n    \u0026date_after=2024-11-02T12:30:12.081598Z\n    \u0026page=1\n    \u0026page_size=20\n    \u0026confirmed=true\n```\n\n#### Response:\n```json\n{\n  \"page\": 1,\n  \"page_size\": 20,\n  \"total\": 2,\n  \"total_pages\": 1,\n  \"records\": [\n    {\n      \"id\": \"409e3900-c190-4dad-882d-ec2d40245329\",\n      \"first_name\": \"John\",\n      \"last_name\": \"Smith\",\n      \"email\": \"john.smith@example.com\",\n      \"confirmed\": true,\n      \"created_at\": \"2024-11-03T12:30:12.081598Z\",\n      \"updated_at\": \"2024-11-03T12:30:12.081598Z\"\n    },\n    {\n      \"id\": \"9167d825-8944-4428-bf91-3c5531728b5e\",\n      \"first_name\": \"Johnny\",\n      \"last_name\": \"Doe\",\n      \"email\": \"johnny.doe@example.com\",\n      \"confirmed\": true,\n      \"created_at\": \"2024-10-28T19:14:49.064626Z\",\n      \"updated_at\": \"2024-10-28T19:14:49.064626Z\"\n    }\n  ]\n}\n```\n\n2. ### Date range filter combined with two other custom filters\n\n- Notice the `confirmed=true` and `first_name=Alex` filters.\n- For the `first_name` filter the value will be an exact match (case-sensitive).\n- You can extend your struct as you please while the query parameters will also be available automatically. \n\n#### Request:\n```\nGET /v1/internal/users\n    ?date_before=2024-11-03T12:30:12.081598Z\n    \u0026date_after=2024-11-02T12:30:12.081598Z\n    \u0026confirmed=true\n    \u0026first_name=Alex\n```\n\n#### Response:\n```json\n{\n  \"page\": 1,\n  \"page_size\": 20,\n  \"total\": 1,\n  \"total_pages\": 1,\n  \"records\": [\n    {\n      \"id\": \"509e3900-c190-4dad-882d-ec2d40245329\",\n      \"first_name\": \"Alex\",\n      \"last_name\": \"Johnson\",\n      \"email\": \"alex.johnson@example.com\",\n      \"confirmed\": true,\n      \"created_at\": \"2024-11-02T12:30:12.081598Z\"\n    }\n  ]\n}\n```\n\n## Performance Considerations\n\n### Query Pattern Optimization\n| Query Pattern | Impact | Recommendation |\n|--------------|---------|----------------|\n| SELECT * | ❌ High Impact | Specify needed columns |\n| Large Text Columns | ❌ High Impact | Use separate detail endpoint |\n| Computed Columns | ⚠️ Medium Impact | Cache if possible |\n| JSON Aggregation | ⚠️ Medium Impact | Limit array size |\n\n### Recommended Indexes\n```sql\n-- Text search\nCREATE INDEX idx_users_name_gin ON users USING gin(to_tsvector('english', name));\n\n-- Composite indexes for common queries\nCREATE INDEX idx_users_confirmed_created ON users(confirmed, created_at);\n\n-- JSON indexes\nCREATE INDEX idx_users_metadata ON users USING gin(metadata);\n```\n\n### Pagination Performance\n| Page Size | Records | Performance Impact |\n|-----------|---------|-------------------|\n| 1-10      | Optimal | ✅ Best           |\n| 11-50     | Good    | ✅ Good           |\n| 51-100    | Caution | ⚠️ Monitor        |\n| 100+      | Poor    | ❌ Not Recommended |\n\n\n## Security Features\n\n### Input Sanitization\n- Search terms are cleaned and normalized\n- Parameter input values are trimmed and/or clamped against their defaults\n- Column names are validated against an allowlist:\n  - The struct itself first;\n  - Database specific table names second;\n- SQL injection patterns are blocked\n- System table access is prevented\n\n### Protected Patterns\n- System schemas (pg_, information_schema)\n- System columns (oid, xmin, etc.)\n- SQL injection attempts\n- Invalid characters in identifiers\n\n## Contributing\n\nI warmly welcome contributions from the community! \nIf you have ideas, improvements, or fixes, we encourage you to submit a Pull Request. \nYour input is highly valued, and I'm excited to collaborate with you to make this project even better.\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexandrughinea%2Fsqlx-paginated","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexandrughinea%2Fsqlx-paginated","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexandrughinea%2Fsqlx-paginated/lists"}