{"id":25974774,"url":"https://github.com/tomakado/dumbql","last_synced_at":"2025-09-06T05:42:05.432Z","repository":{"id":277062429,"uuid":"931212877","full_name":"tomakado/dumbql","owner":"tomakado","description":"Simple (dumb?) query language","archived":false,"fork":false,"pushed_at":"2025-06-16T12:21:51.000Z","size":149,"stargazers_count":46,"open_issues_count":6,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-16T13:32:11.855Z","etag":null,"topics":["dsl","parser","query-language","sql"],"latest_commit_sha":null,"homepage":"https://go.tomakado.io/dumbql","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"defer-panic/dumbql","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tomakado.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":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-02-11T22:38:54.000Z","updated_at":"2025-06-16T12:21:48.000Z","dependencies_parsed_at":"2025-04-06T12:24:00.036Z","dependency_job_id":"7915d232-99bb-451e-96b0-062c9c4e3222","html_url":"https://github.com/tomakado/dumbql","commit_stats":null,"previous_names":["tomakado/dumbql"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/tomakado/dumbql","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomakado%2Fdumbql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomakado%2Fdumbql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomakado%2Fdumbql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomakado%2Fdumbql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomakado","download_url":"https://codeload.github.com/tomakado/dumbql/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomakado%2Fdumbql/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267505099,"owners_count":24098346,"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","status":"online","status_checked_at":"2025-07-28T02:00:09.689Z","response_time":68,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["dsl","parser","query-language","sql"],"created_at":"2025-03-05T02:33:46.273Z","updated_at":"2025-07-28T11:03:22.256Z","avatar_url":"https://github.com/tomakado.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\u003ch1\u003eDumbQL\u003c/h1\u003e\n    \n![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/tomakado/dumbql) ![GitHub License](https://img.shields.io/github/license/tomakado/dumbql) ![GitHub Tag](https://img.shields.io/github/v/tag/tomakado/dumbql) [![Go Report Card](https://goreportcard.com/badge/go.tomakado.io/dumbql)](https://goreportcard.com/report/go.tomakado.io/dumbql) [![CI](https://github.com/tomakado/dumbql/actions/workflows/main.yml/badge.svg)](https://github.com/tomakado/dumbql/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/tomakado/dumbql/graph/badge.svg?token=15IWJO0R0K)](https://codecov.io/gh/tomakado/dumbql) [![Go Reference](https://pkg.go.dev/badge/go.tomakado.io/dumbql.svg)](https://pkg.go.dev/go.tomakado.io/dumbql)\n\nSimple (dumb?) query language and parser for Go.\n\n\u003c/div\u003e\n\n## Features\n\n- Field expressions (`age \u003e= 18`, `field.name:\"field value\"`, etc.)\n- Boolean expressions (`age \u003e= 18 and city = Barcelona`, `occupation = designer or occupation = \"ux analyst\"`)\n- One-of/In expressions (`occupation = [designer, \"ux analyst\"]`)\n- Boolean fields support with shorthand syntax (`is_active`, `verified and premium`)\n- Schema validation\n- Drop-in usage with [squirrel](https://github.com/Masterminds/squirrel) or SQL drivers directly\n- Struct matching with `dumbql` struct tag\n    - Via reflection (slow but works out of box)\n    - Via [code generation](./cmd/dumbqlgen/README.md)\n\n## Examples\n\n### Simple parse\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n\n    \"go.tomakado.io/dumbql\"\n)\n\nfunc main() {\n    const q = `profile.age \u003e= 18 and profile.city = Barcelona`\n    ast, err := dumbql.Parse(q)\n    if err != nil {\n        panic(err)\n    }\n\n    fmt.Println(ast)\n    // Output: (and (\u003e= profile.age 18) (= profile.city \"Barcelona\"))\n}\n```\n\n### Validation against schema\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n\n    \"go.tomakado.io/dumbql\"\n    \"go.tomakado.io/dumbql/schema\"\n)\n\nfunc main() {\n    schm := schema.Schema{\n        \"status\": schema.All(\n            schema.Is[string](),\n            schema.EqualsOneOf(\"pending\", \"approved\", \"rejected\"),\n        ),\n        \"period_months\": schema.Max(int64(3)),\n        \"title\":         schema.LenInRange(1, 100),\n    }\n\n    // The following query is invalid against the schema:\n    // \t- period_months == 4, but max allowed value is 3\n    // \t- field `name` is not described in the schema\n    //\n    // Invalid parts of the query are dropped.\n    const q = `status:pending and period_months:4 and (title:\"hello world\" or name:\"John Doe\")`\n    expr, err := dumbql.Parse(q)\n    if err != nil {\n        panic(err)\n    }\n\n    validated, err := expr.Validate(schm)\n    fmt.Println(validated)\n    fmt.Printf(\"validation error: %v\\n\", err)\n    // Output: \n    // (and (= status \"pending\") (= title \"hello world\"))\n    // validation error: field \"period_months\": value must be equal or less than 3, got 4; field \"name\" not found in schema\n}\n```\n\n### Convert to SQL\n\n```go\npackage main\n\nimport (\n  \"fmt\"\n\n  sq \"github.com/Masterminds/squirrel\"\n  \"go.tomakado.io/dumbql\"\n)\n\nfunc main() {\n  const q = `status:pending and period_months \u003c 4 and (title:\"hello world\" or name:\"John Doe\")`\n  expr, err := dumbql.Parse(q)\n  if err != nil {\n    panic(err)\n  }\n\n  sql, args, err := sq.Select(\"*\").\n    From(\"users\").\n    Where(expr).\n    ToSql()\n  if err != nil {\n    panic(err)\n  }\n\n  fmt.Println(sql)\n  fmt.Println(args)\n  // Output: \n  // SELECT * FROM users WHERE ((status = ? AND period_months \u003c ?) AND (title = ? OR name = ?))\n  // [pending 4 hello world John Doe]\n}\n```\n\nSee [dumbql_example_test.go](dumbql_example_test.go)\n\n### Match against structs\n\n```go\npackage main\n\nimport (\n  \"fmt\"\n\n  \"go.tomakado.io/dumbql\"\n  \"go.tomakado.io/dumbql/match\"\n  \"go.tomakado.io/dumbql/query\"\n)\n\ntype User struct {\n  ID       int64   `dumbql:\"id\"`\n  Name     string  `dumbql:\"name\"`\n  Age      int64   `dumbql:\"age\"`\n  Score    float64 `dumbql:\"score\"`\n  Location string  `dumbql:\"location\"`\n  Role     string  `dumbql:\"role\"`\n}\n\nfunc main() {\n  users := []User{\n    {\n      ID:       1,\n      Name:     \"John Doe\",\n      Age:      30,\n      Score:    4.5,\n      Location: \"New York\",\n      Role:     \"admin\",\n    },\n    {\n      ID:       2,\n      Name:     \"Jane Smith\",\n      Age:      25,\n      Score:    3.8,\n      Location: \"Los Angeles\",\n      Role:     \"user\",\n    },\n    {\n      ID:       3,\n      Name:     \"Bob Johnson\",\n      Age:      35,\n      Score:    4.2,\n      Location: \"Chicago\",\n      Role:     \"user\",\n    },\n    // This one will be dropped:\n    {\n      ID:       4,\n      Name:     \"Alice Smith\",\n      Age:      25,\n      Score:    3.8,\n      Location: \"Los Angeles\",\n      Role:     \"admin\",\n    },\n  }\n\n  q := `(age \u003e= 30 and score \u003e 4.0) or (location:\"Los Angeles\" and role:\"user\")`\n  ast, err := dumbql.Parse(q)\n  if err != nil {\n    panic(err)\n  }\n  matcher := \u0026match.StructMatcher{}\n  filtered := make([]User, 0, len(users))\n\n  for _, user := range users {\n    if expr.Match(\u0026user, matcher) {\n      filtered = append(filtered, user)\n    }\n  }\n\n  fmt.Println(filtered)\n  // [{1 John Doe 30 4.5 New York admin} {2 Jane Smith 25 3.8 Los Angeles user} {3 Bob Johnson 35 4.2 Chicago user}]\n}\n```\n\nSee [match_example_test.go](match_example_test.go) for more examples.\n\n## Query syntax\n\nThis section is a non-formal description of DumbQL syntax. For strict description see [grammar file](query/grammar.peg).\n\n### Field expression\n\nField name \u0026 value pair divided by operator. Field name is any alphanumeric identifier (with underscore), value can be string, int64, float64, or bool.\nOne-of expression is also supported (see below).\n\n```\n\u003cfield_name\u003e \u003coperator\u003e \u003cvalue\u003e\n```\n\nfor example\n\n```\nperiod_months \u003c 4\nis_active:true\n```\n\n### Field expression operators\n\n| Operator             | Meaning                       | Supported types                      |\n|----------------------|-------------------------------|--------------------------------------|\n| `:` or `=`           | Equal, one of                 | `int64`, `float64`, `string`, `bool` |\n| `!=` or `!:`         | Not equal                     | `int64`, `float64`, `string`, `bool` |\n| `~`                  | \"Like\" or \"contains\" operator | `string`                             |\n| `\u003e`, `\u003e=`, `\u003c`, `\u003c=` | Comparison                    | `int64`, `float64`                   |\n| `?` or `exists`      | Field exists and is not zero  | All types                            |\n\n\n### Boolean operators\n\nMultiple field expression can be combined into boolean expressions with `and` (`AND`) or `or` (`OR`) operators:\n\n```\nstatus:pending and period_months \u003c 4 and (title:\"hello world\" or name:\"John Doe\")\n```\n\n### Boolean Field Shorthand\n\nBoolean fields can be expressed in a simpler shorthand syntax:\n\n```\nverified                  # equivalent to verified:true\nverified and premium      # equivalent to verified:true and premium:true  \nnot verified              # equivalent to not (verified:true)\nverified or admin         # equivalent to verified:true or admin:true\n```\n\n### \"One of\" expression\n\nSometimes instead of multiple `and`/`or` clauses against the same field:\n\n```\noccupation = designer or occupation = \"ux analyst\"\n```\n\nit's more convenient to use equivalent “one of” expressions:\n\n```\noccupation: [designer, \"ux analyst\"]\n```\n\n### Field presence operator\n\nThe field presence operator (`?` or `exists`) checks if a field exists and is not its zero value:\n\n```\nid?              # Checks if ID field exists and is not 0\nname?            # Checks if Name field exists and is not empty string\ndescription?     # Checks if Description field exists and is not empty string\ncount?           # Checks if Count field exists and is not 0\nis_active?       # Checks if IsActive field exists and is not false\namount?          # Checks if Amount field exists and is not 0.0\n```\n\nYou can also use the keyword form: `name exists`\n\nIt can be combined with other operators:\n\n```\nname? and age\u003e20\nnot email?\n```\n\nIn SQL generation, the field presence operator is translated to `IS NOT NULL` clause.\n\n### Numbers\n\nIf number does not have digits after `.` it's treated as integer and stored as `int64`. And it's `float64` otherwise.\n\n### Strings\n\nString is a sequence of Unicode characters surrounded by double quotes (`\"`). In some cases like single word it's possible to write string value without double quotes.\n\n### Booleans\n\nBoolean values are represented by `true` or `false` literals and can be used with the equality operators (`=`, `:`, `!=`, `!:`).\n\n```\nis_active:true\nverified = true\nis_banned != false\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomakado%2Fdumbql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomakado%2Fdumbql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomakado%2Fdumbql/lists"}