{"id":36900626,"url":"https://github.com/zntrio/go-cel2squirrel","last_synced_at":"2026-01-12T15:46:44.470Z","repository":{"id":321763744,"uuid":"1087041389","full_name":"zntrio/go-cel2squirrel","owner":"zntrio","description":"Convert a CEL boolean expression to Squirrel SQL Builder for dynamic filtering capabilities.","archived":false,"fork":false,"pushed_at":"2025-12-03T12:58:38.000Z","size":62,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-06T16:48:09.923Z","etag":null,"topics":["aip","cel","golang-library","sql","sql-builder"],"latest_commit_sha":null,"homepage":"https://zntr.io/cel2squirrel","language":"Go","has_issues":true,"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/zntrio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-31T09:36:46.000Z","updated_at":"2025-12-03T12:58:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"75b84843-2f1e-4062-93f7-f5bb4f0df501","html_url":"https://github.com/zntrio/go-cel2squirrel","commit_stats":null,"previous_names":["zntrio/go-cel2squirrel"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/zntrio/go-cel2squirrel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zntrio%2Fgo-cel2squirrel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zntrio%2Fgo-cel2squirrel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zntrio%2Fgo-cel2squirrel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zntrio%2Fgo-cel2squirrel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zntrio","download_url":"https://codeload.github.com/zntrio/go-cel2squirrel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zntrio%2Fgo-cel2squirrel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28341820,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T12:22:26.515Z","status":"ssl_error","status_checked_at":"2026-01-12T12:22:10.856Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["aip","cel","golang-library","sql","sql-builder"],"created_at":"2026-01-12T15:46:44.360Z","updated_at":"2026-01-12T15:46:44.460Z","avatar_url":"https://github.com/zntrio.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cel2squirrel\n\n[![Go Reference](https://pkg.go.dev/badge/zntr.io/cel2squirrel.svg)](https://pkg.go.dev/zntr.io/cel2squirrel)\n[![Go Report Card](https://goreportcard.com/badge/zntr.io/cel2squirrel)](https://goreportcard.com/report/zntr.io/cel2squirrel)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nA Go package that converts Common Expression Language (CEL) expressions to SQL WHERE clauses using the Squirrel SQL builder.\n\n## Overview\n\n`cel2squirrel` enables filtering using CEL expressions (as specified in [AIP-160](https://google.aip.dev/160)) and converts them to SQL queries that can be executed against a database. It validates that expressions are boolean and provides type-safe conversion to Squirrel SQL builder objects.\n\n## Features\n\n- ✅ **CEL Expression Parsing**: Parse and validate CEL expressions\n- ✅ **Boolean Validation**: Ensure expressions return boolean values\n- ✅ **Type-Safe Conversion**: Convert CEL AST to Squirrel SQL builder\n- ✅ **Field Mapping**: Map CEL field names to SQL column names\n- ✅ **Operator Support**:\n  - Comparison: `==`, `!=`, `\u003c`, `\u003c=`, `\u003e`, `\u003e=`\n  - Logical: `\u0026\u0026` (AND), `||` (OR), `!` (NOT)\n  - String: `contains()`, `startsWith()`, `endsWith()`\n  - Membership: `in` operator\n  - Null: `== null`, `!= null`\n- ✅ **PostgreSQL Compatible**: Works with PostgreSQL placeholder format (`$1`, `$2`, etc.)\n- 🔒 **Security Hardened**: SQL injection protection, DoS prevention, field-level authorization\n\n## Installation\n\n```bash\ngo get zntr.io/cel2squirrel\n```\n\n## Usage\n\n### Basic Example\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"log\"\n    \n    \"github.com/Masterminds/squirrel\"\n    \"github.com/google/cel-go/cel\"\n    \"zntr.io/cel2squirrel\"\n)\n\nfunc main() {\n    // Define field declarations (CEL variables and their types)\n    config := cel2squirrel.Config{\n        FieldDeclarations: map[string]*cel.Type{\n            \"status\": cel.StringType,\n            \"age\":    cel.IntType,\n            \"rating\": cel.DoubleType,\n        },\n    }\n    \n    // Create converter\n    converter, err := cel2squirrel.NewConverter(config)\n    if err != nil {\n        log.Fatal(err)\n    }\n    \n    // Convert CEL expression to SQL\n    celExpr := `status == \"published\" \u0026\u0026 age \u003e= 18`\n    result, err := converter.Convert(celExpr, nil)\n    if err != nil {\n        log.Fatal(err)\n    }\n    \n    // Build complete query with Squirrel\n    query := squirrel.Select(\"*\").\n        From(\"users\").\n        Where(result.Where)\n    \n    sql, args, _ := query.ToSql()\n    fmt.Println(sql)\n    // Output: SELECT * FROM users WHERE (status = ? AND age \u003e= ?)\n    fmt.Println(args)\n    // Output: [published 18]\n}\n```\n\n### With Field Mappings\n\nMap CEL field names to different SQL column names:\n\n```go\nconfig := cel2squirrel.Config{\n    FieldDeclarations: map[string]*cel.Type{\n        \"isDraft\": cel.BoolType,\n        \"ownerId\": cel.StringType,\n    },\n}\n\nconverter, _ := cel2squirrel.NewConverter(config)\n\n// Map camelCase CEL fields to snake_case SQL columns\nfieldMappings := map[string]string{\n    \"isDraft\": \"is_draft\",\n    \"ownerId\": \"owner_id\",\n}\n\ncelExpr := `isDraft == false \u0026\u0026 ownerId == \"user123\"`\nresult, _ := converter.Convert(celExpr, fieldMappings)\n\nsql, args, _ := result.Where.ToSql()\n// SQL: (is_draft = ? AND owner_id = ?)\n// Args: [false user123]\n```\n\n### PostgreSQL Placeholders\n\nUse PostgreSQL-style numbered placeholders:\n\n```go\nquery := squirrel.Select(\"*\").\n    From(\"users\").\n    Where(result.Where).\n    PlaceholderFormat(squirrel.Dollar)\n\nsql, args, _ := query.ToSql()\n// SQL: SELECT * FROM users WHERE (status = $1 AND age \u003e= $2)\n```\n\n### Complex Expressions\n\nHandle complex nested boolean expressions:\n\n```go\ncelExpr := `(status == \"published\" || status == \"featured\") \u0026\u0026 age \u003e= 18 \u0026\u0026 rating \u003e 4.0`\nresult, _ := converter.Convert(celExpr, nil)\n\nquery := squirrel.Select(\"id\", \"name\", \"rating\").\n    From(\"users\").\n    Where(result.Where).\n    OrderBy(\"rating DESC\").\n    Limit(10)\n\nsql, args, _ := query.ToSql()\n// SQL: SELECT id, name, rating FROM users \n//      WHERE ((status = ? OR status = ?) AND age \u003e= ? AND rating \u003e ?) \n//      ORDER BY rating DESC LIMIT 10\n// Args: [published featured 18 4.0]\n```\n\n### String Operations\n\nUse CEL string methods:\n\n```go\n// Contains\ncelExpr := `name.contains(\"john\")`\n// SQL: name LIKE ?\n// Args: [%john%]\n\n// Starts with\ncelExpr := `name.startsWith(\"Dr. \")`\n// SQL: name LIKE ?\n// Args: [Dr. %]\n\n// Ends with\ncelExpr := `email.endsWith(\"@example.com\")`\n// SQL: email LIKE ?\n// Args: [%@example.com]\n```\n\n### Null Comparisons\n\nHandle NULL values:\n\n```go\n// IS NULL\ncelExpr := `deletedAt == null`\n// SQL: deletedAt IS NULL\n\n// IS NOT NULL\ncelExpr := `deletedAt != null`\n// SQL: deletedAt IS NOT NULL\n```\n\n### IN Operator\n\nFilter with multiple values:\n\n```go\ncelExpr := `status in [\"published\", \"featured\", \"archived\"]`\nresult, _ := converter.Convert(celExpr, nil)\n\nsql, args, _ := result.Where.ToSql()\n// SQL: status IN (?,?,?)\n// Args: [published featured archived]\n```\n\n## Real-World Example\n\nExample implementation of a database repository with CEL filtering (AIP-160 compliant):\n\n```go\npackage repository\n\nimport (\n    \"context\"\n    \"fmt\"\n    \n    \"github.com/Masterminds/squirrel\"\n    \"github.com/google/cel-go/cel\"\n    \"zntr.io/cel2squirrel\"\n)\n\ntype UserRepository struct {\n    db        *sql.DB\n    converter *cel2squirrel.Converter\n}\n\nfunc NewUserRepository(db *sql.DB) (*UserRepository, error) {\n    // Setup CEL converter\n    config := cel2squirrel.Config{\n        FieldDeclarations: map[string]*cel.Type{\n            \"status\":     cel.StringType,\n            \"isActive\":   cel.BoolType,\n            \"rating\":     cel.DoubleType,\n            \"createTime\": cel.TimestampType,\n        },\n    }\n    \n    converter, err := cel2squirrel.NewConverter(config)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to create CEL converter: %w\", err)\n    }\n    \n    return \u0026UserRepository{\n        db:        db,\n        converter: converter,\n    }, nil\n}\n\nfunc (r *UserRepository) Search(ctx context.Context, filter string, pageSize int) ([]User, error) {\n    // Map CEL fields to SQL columns\n    fieldMappings := map[string]string{\n        \"isActive\":   \"is_active\",\n        \"createTime\": \"create_time\",\n    }\n    \n    // Convert CEL filter to SQL\n    result, err := r.converter.Convert(filter, fieldMappings)\n    if err != nil {\n        return nil, fmt.Errorf(\"invalid filter expression: %w\", err)\n    }\n    \n    // Build query with Squirrel\n    query := squirrel.Select(\"*\").\n        From(\"users\").\n        Where(result.Where).\n        PlaceholderFormat(squirrel.Dollar).\n        Limit(uint64(pageSize))\n    \n    sql, args, err := query.ToSql()\n    if err != nil {\n        return nil, err\n    }\n    \n    // Execute query\n    rows, err := r.db.QueryContext(ctx, sql, args...)\n    if err != nil {\n        return nil, err\n    }\n    defer rows.Close()\n    \n    // Scan results...\n    var users []User\n    for rows.Next() {\n        var u User\n        // Scan into user struct...\n        users = append(users, u)\n    }\n    \n    return users, rows.Err()\n}\n```\n\n### API Usage Example\n\n```http\nGET /v1/users?filter=status == \"active\" \u0026\u0026 rating \u003e 4.0\u0026page_size=20\n```\n\n## Supported CEL Operations\n\n### Comparison Operators\n\n| CEL Operator | SQL Equivalent | Example |\n|--------------|----------------|---------|\n| `==` | `=` | `status == \"published\"` |\n| `!=` | `\u003c\u003e` or `IS NOT` | `status != \"draft\"` |\n| `\u003c` | `\u003c` | `age \u003c 18` |\n| `\u003c=` | `\u003c=` | `age \u003c= 21` |\n| `\u003e` | `\u003e` | `age \u003e 65` |\n| `\u003e=` | `\u003e=` | `rating \u003e= 4.5` |\n\n### Logical Operators\n\n| CEL Operator | SQL Equivalent | Example |\n|--------------|----------------|---------|\n| `\u0026\u0026` | `AND` | `status == \"published\" \u0026\u0026 age \u003e= 18` |\n| `\\|\\|` | `OR` | `status == \"draft\" \\|\\| status == \"published\"` |\n| `!` | `NOT` | `!(isDraft)` |\n\n### String Operations\n\n| CEL Function | SQL Equivalent | Example |\n|--------------|----------------|---------|\n| `contains(x)` | `LIKE '%x%'` | `label.contains(\"test\")` |\n| `startsWith(x)` | `LIKE 'x%'` | `label.startsWith(\"prod\")` |\n| `endsWith(x)` | `LIKE '%x'` | `label.endsWith(\"v2\")` |\n\n### Membership Operators\n\n| CEL Operator | SQL Equivalent | Example |\n|--------------|----------------|---------|\n| `in` | `IN (...)` | `status in [\"published\", \"featured\"]` |\n\n### Null Comparisons\n\n| CEL Expression | SQL Equivalent | Example |\n|----------------|----------------|---------|\n| `field == null` | `IS NULL` | `deletedAt == null` |\n| `field != null` | `IS NOT NULL` | `deletedAt != null` |\n\n## Error Handling\n\nThe converter validates expressions and returns descriptive errors:\n\n```go\n// Non-boolean expression\ncelExpr := `age + 5`  // Returns: \"CEL expression must return a boolean, got int\"\n\n// Undefined field\ncelExpr := `unknownField == \"value\"`  // Returns: \"failed to compile CEL expression: undeclared reference...\"\n\n// Syntax error\ncelExpr := `status == `  // Returns: \"failed to compile CEL expression: Syntax error...\"\n```\n\n## Type Declarations\n\nUse CEL types directly to define field types:\n\n```go\nimport \"github.com/google/cel-go/cel\"\n\nFieldDeclarations: map[string]*cel.Type{\n    \"stringField\":    cel.StringType,\n    \"intField\":       cel.IntType,\n    \"doubleField\":    cel.DoubleType,\n    \"boolField\":      cel.BoolType,\n    \"uintField\":      cel.UintType,\n    \"timestampField\": cel.TimestampType,\n    \"durationField\":  cel.DurationType,\n    \"bytesField\":     cel.BytesType,\n    \n    // Complex types\n    \"stringList\":     cel.ListType(cel.StringType),\n    \"stringMap\":      cel.MapType(cel.StringType, cel.StringType),\n}\n```\n\n## Limitations\n\n- **No Function Calls**: Custom CEL functions are not supported (only built-in string methods)\n- **Simple Expressions**: Complex nested member access is limited\n- **List Literals Only**: The `in` operator requires constant list literals\n- **No Arithmetic in Filters**: Expressions like `age + 5 \u003e 30` are not supported\n\n## Performance Considerations\n\n- **Prepared Statements**: The generated SQL is parameterized and suitable for prepared statements\n- **Validation**: CEL expressions are validated before conversion, preventing SQL injection\n- **Caching**: Consider caching converter instances for frequently used field declarations\n\n## Security\n\n### Overview\n\nThe cel2squirrel package implements multiple layers of security to protect against common vulnerabilities when converting user-supplied filter expressions to SQL queries. All security features are enabled by default with secure defaults.\n\n### SQL Injection Protection\n\n**LIKE Pattern Escaping**: The package automatically escapes SQL special characters (`%`, `_`, `\\`, `[`, `]`) in string operations to prevent LIKE pattern injection:\n\n```go\n// User input: name.contains(\"%\")\n// Unsafe: LIKE '%%%'  (matches all records!)\n// Safe:   LIKE '%\\%%' (matches literal % character)\n\ncelExpr := `name.contains(\"%admin\")`\nresult, _ := converter.Convert(celExpr)\n// Generates: name LIKE ?\n// Args: [%\\%admin%]  // Special chars properly escaped\n```\n\n**Parameterized Queries**: All values are passed as query parameters, never concatenated into SQL strings:\n\n```go\ncelExpr := `status == \"published\" \u0026\u0026 age \u003e 18`\n// Generates parameterized SQL: status = ? AND age \u003e ?\n// Args: [\"published\", 18]\n```\n\n### Denial of Service Prevention\n\nConfigure expression complexity limits to prevent resource exhaustion:\n\n```go\nconfig := cel2squirrel.Config{\n    FieldDeclarations: map[string]cel2squirrel.ColumnMapping{\n        \"status\": {Type: cel.StringType, Column: \"status\"},\n    },\n\n    // Security limits (default values shown)\n    MaxExpressionLength: 10000,  // Max 10KB expression\n    MaxExpressionDepth:  50,     // Max 50 levels of nesting\n    MaxInClauseSize:     1000,   // Max 1000 values in IN clause\n}\n\nconverter, _ := cel2squirrel.NewConverter(config)\n\n// These will be rejected:\n_, err := converter.Convert(strings.Repeat(\"a\", 20000))  // Too long\n_, err := converter.Convert(deeplyNestedExpression)       // Too deep\n_, err := converter.Convert(`status in [...]`)            // Too many values\n```\n\n### Field-Level Authorization\n\nRestrict which fields users can filter by based on their roles:\n\n```go\nconfig := cel2squirrel.Config{\n    FieldDeclarations: map[string]cel2squirrel.ColumnMapping{\n        \"status\":   {Type: cel.StringType, Column: \"status\"},\n        \"owner_id\": {Type: cel.StringType, Column: \"owner_id\"},\n        \"salary\":   {Type: cel.IntType, Column: \"salary\"},\n    },\n\n    // Public fields accessible to all users\n    PublicFields: []string{\"status\"},\n\n    // Role-based access control\n    FieldACL: map[string][]string{\n        \"owner_id\": {\"admin\", \"manager\"},\n        \"salary\":   {\"admin\", \"hr\"},\n    },\n}\n\nconverter, _ := cel2squirrel.NewConverter(config)\n\n// Check authorization before conversion\nuserRoles := []string{\"user\"}\n_, err := converter.ConvertWithAuth(`owner_id == \"user123\"`, userRoles)\n// Returns: \"access denied: insufficient permissions\"\n\nadminRoles := []string{\"admin\"}\nresult, _ := converter.ConvertWithAuth(`owner_id == \"user123\"`, adminRoles)\n// Success: admin can filter by owner_id\n```\n\n### Error Message Sanitization\n\nThe package sanitizes error messages to prevent information disclosure:\n\n```go\n// Internal error with details (logged server-side)\nconvErr := err.(*cel2squirrel.ConversionError)\nlog.Printf(\"Conversion failed: %v\", convErr.InternalError)\n// Logs: \"undeclared reference to 'secretField'\"\n\n// Public error message (returned to user)\nfmt.Println(convErr.Error())\n// Returns: \"invalid filter expression syntax\"\n// Does NOT reveal field names or internal structure\n```\n\n### Runtime Type Validation\n\nDefense-in-depth type checking validates that values match declared field types:\n\n```go\nconfig := cel2squirrel.Config{\n    FieldDeclarations: map[string]cel2squirrel.ColumnMapping{\n        \"age\": {Type: cel.IntType, Column: \"age\"},\n    },\n}\n\nconverter, _ := cel2squirrel.NewConverter(config)\n\n// CEL's type system catches this at compile time\n_, err := converter.Convert(`age == \"not a number\"`)\n// Returns type mismatch error\n\n// Runtime validation provides additional protection\n```\n\n### Secure Configuration Example\n\nA production-ready secure configuration:\n\n```go\nfunc NewSecureConverter() (*cel2squirrel.Converter, error) {\n    config := cel2squirrel.DefaultConfig() // Start with secure defaults\n\n    config.FieldDeclarations = map[string]cel2squirrel.ColumnMapping{\n        \"status\":     {Type: cel.StringType, Column: \"status\"},\n        \"created_at\": {Type: cel.TimestampType, Column: \"created_at\"},\n        \"owner_id\":   {Type: cel.StringType, Column: \"owner_id\"},\n        \"is_private\": {Type: cel.BoolType, Column: \"is_private\"},\n    }\n\n    // Define public fields\n    config.PublicFields = []string{\"status\", \"created_at\"}\n\n    // Restrict sensitive fields\n    config.FieldACL = map[string][]string{\n        \"owner_id\":   {\"admin\", \"manager\"},\n        \"is_private\": {\"admin\"},\n    }\n\n    // Adjust limits for your use case\n    config.MaxExpressionLength = 1000  // Shorter limit for API\n    config.MaxExpressionDepth = 10     // Prevent deeply nested attacks\n    config.MaxInClauseSize = 100       // Reasonable batch size\n\n    return cel2squirrel.NewConverter(config)\n}\n\n// Usage in HTTP handler\nfunc (h *Handler) SearchRecords(w http.ResponseWriter, r *http.Request) {\n    filter := r.URL.Query().Get(\"filter\")\n\n    // Get user roles from authentication context\n    userRoles := h.getUserRoles(r.Context())\n\n    // Convert with authorization check\n    result, err := h.converter.ConvertWithAuth(filter, userRoles)\n    if err != nil {\n        if convErr, ok := err.(*cel2squirrel.ConversionError); ok {\n            // Return sanitized error to user\n            http.Error(w, convErr.PublicMessage, http.StatusBadRequest)\n\n            // Log detailed error server-side\n            log.Printf(\"Filter conversion failed: %v (code=%s)\",\n                convErr.InternalError, convErr.ErrorCode)\n            return\n        }\n        http.Error(w, \"Invalid filter\", http.StatusBadRequest)\n        return\n    }\n\n    // Build and execute query...\n}\n```\n\n### Security Best Practices\n\n1. **Always use field-level authorization** for multi-tenant or multi-user systems\n2. **Log security events** to detect attack patterns\n3. **Set conservative limits** for expression complexity based on your use case\n4. **Never expose internal errors** to end users\n5. **Use ConvertWithAuth()** instead of Convert() when authorization is configured\n6. **Validate user roles** from trusted authentication context, never from user input\n7. **Monitor for unusual patterns** like deeply nested expressions or large IN clauses\n8. **Test security controls** regularly with security-focused test cases\n9. **Keep dependencies updated** (CEL, Squirrel) for security patches\n\n## Testing\n\nRun tests:\n\n```bash\ngo test -v ./...\n```\n\nRun with coverage:\n\n```bash\ngo test -cover ./...\n```\n\nRun fuzz tests:\n\n```bash\ngo test -fuzz=Fuzz -fuzztime=30s\n```\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.\n\n### Development Setup\n\n1. Clone the repository\n2. Install dependencies: `go mod download`\n3. Run tests: `go test ./...`\n4. Run linter: `go vet ./...`\n\n### Guidelines\n\n- Write tests for new features\n- Follow Go best practices and idioms\n- Update documentation for API changes\n- Ensure all tests pass before submitting PR\n\n## Use Cases\n\nThis library is ideal for:\n\n- **REST APIs**: Implement AIP-160 compliant filtering in your API endpoints\n- **Multi-tenant Applications**: Safe, field-level authorization for database queries\n- **Admin Dashboards**: Flexible search and filtering capabilities\n- **Data Export Tools**: User-defined filtering without SQL injection risks\n- **GraphQL Backends**: Convert filter arguments to SQL queries\n\n## References\n\n- [Google AIP-160: Filtering](https://google.aip.dev/160)\n- [CEL Specification](https://github.com/google/cel-spec)\n- [CEL Go Library](https://github.com/google/cel-go)\n- [Squirrel SQL Builder](https://github.com/Masterminds/squirrel)\n\n## License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n\n## Support\n\n- Report issues on [GitHub Issues](https://github.com/zntr/go-cel2squirrel/issues)\n- For questions and discussions, use [GitHub Discussions](https://github.com/zntr/go-cel2squirrel/discussions)\n\n## Acknowledgments\n\nBuilt with:\n- [google/cel-go](https://github.com/google/cel-go) - Common Expression Language implementation\n- [Masterminds/squirrel](https://github.com/Masterminds/squirrel) - SQL query builder\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzntrio%2Fgo-cel2squirrel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzntrio%2Fgo-cel2squirrel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzntrio%2Fgo-cel2squirrel/lists"}