{"id":25403128,"url":"https://github.com/shijbey/drolta_py","last_synced_at":"2026-02-26T09:59:25.077Z","repository":{"id":275692217,"uuid":"925516929","full_name":"ShiJbey/drolta_py","owner":"ShiJbey","description":"A logic programming-inspired SQLite query language for simplified and composable queries in Python","archived":false,"fork":false,"pushed_at":"2025-02-12T15:51:23.000Z","size":206,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-12T16:41:37.540Z","etag":null,"topics":["database","python","query-builder","query-engine","sqlite","sqlite3"],"latest_commit_sha":null,"homepage":"","language":"Python","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/ShiJbey.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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}},"created_at":"2025-02-01T03:49:49.000Z","updated_at":"2025-02-12T15:51:26.000Z","dependencies_parsed_at":null,"dependency_job_id":"e8b3bd16-3b8d-4ea7-bf06-ff865b9fd3bf","html_url":"https://github.com/ShiJbey/drolta_py","commit_stats":null,"previous_names":["shijbey/drolta_py"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShiJbey%2Fdrolta_py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShiJbey%2Fdrolta_py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShiJbey%2Fdrolta_py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShiJbey%2Fdrolta_py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ShiJbey","download_url":"https://codeload.github.com/ShiJbey/drolta_py/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239080042,"owners_count":19578105,"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":["database","python","query-builder","query-engine","sqlite","sqlite3"],"created_at":"2025-02-16T02:28:00.401Z","updated_at":"2025-10-30T23:31:12.712Z","avatar_url":"https://github.com/ShiJbey.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Drolta: A SQLite Query Engine for Python\n\n![Package Version badge](https://img.shields.io/pypi/v/drolta)\n![Supported Python Versions badge](https://img.shields.io/pypi/pyversions/drolta)\n![License badge](https://img.shields.io/pypi/l/drolta)\n![Monthly downloads badge](https://img.shields.io/pypi/dm/drolta)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat\u0026labelColor=ef8336)](https://pycqa.github.io/isort/)\n[![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch)\n\nDrolta is an experimental SQLite query engine for wiring more simplified, composable, and declarative queries using a logic programming-inspired syntax. Drolta was initially developed to simplify querying social simulation data generated by NPC interactions in simulation-focused narrative games. SQL, while powerful, is not the most user-friendly language for beginners. We found it challenging to determine how to properly join tables when searching for complex patterns that need to match relationship patterns with specific event sequences. Additionally, we found that SQL queries could quickly become unwieldy and difficult to understand\nas more tables and common table expressions were added.\n\nDrolta was developed to simplify this process and provide an alternative to raw SQL that is more accessible to a broader audience of game designers. Drolta pulls inspiration from logic programming languages and technologies like [Prolog](https://en.wikipedia.org/wiki/Prolog), [Datomic](https://docs.datomic.com/datomic-overview.html),\nand [TED](https://github.com/ianhorswill/TED). It abstracts away the complexities of joining tables, allowing users to focus on specifying what variables they care about and how they should match (unify) within the query. The result is a query expression that is much more concise and legible than the raw SQL equivalent.\n\n\u003e [!NOTE]\n\u003e This is an experimental project. There will be bugs, and potentially breaking changes between feature-level releases. If there is enough interest in Drolta, I may invest more time into producing a stable API. Feel free to try it in your projects. Open a new GitHub issue if you find bugs or unexpected behavior. Thank you for trying Drolta.\n\n## Table of Contents\n\n- [Drolta: A SQLite Query Engine for Python](#drolta-a-sqlite-query-engine-for-python)\n  - [Table of Contents](#table-of-contents)\n  - [📖 Documentation](#-documentation)\n    - [Why Drolta?](#why-drolta)\n    - [Installation](#installation)\n    - [Getting Started](#getting-started)\n    - [Design Philosophy](#design-philosophy)\n    - [Queries](#queries)\n    - [Predicates](#predicates)\n    - [Rules](#rules)\n    - [Table/Rule Aliases](#tablerule-aliases)\n    - [Output Variable Aliases](#output-variable-aliases)\n    - [Filter Statements](#filter-statements)\n      - [Comparison Filtering](#comparison-filtering)\n      - [Membership filtering](#membership-filtering)\n    - [NULL Checking](#null-checking)\n    - [AND-Statements](#and-statements)\n    - [OR-Statements](#or-statements)\n    - [NOT-Statements](#not-statements)\n    - [ORDER BY](#order-by)\n    - [GROUP BY](#group-by)\n    - [LIMIT](#limit)\n    - [Aggregation Functions](#aggregation-functions)\n    - [Comments](#comments)\n  - [💢 Troubleshooting](#-troubleshooting)\n    - [OperationalError: database is locked](#operationalerror-database-is-locked)\n  - [🧪 Running Unit Tests](#-running-unit-tests)\n  - [🎨 Syntax Highlighting Support](#-syntax-highlighting-support)\n  - [🏆 Ports and Applications](#-ports-and-applications)\n  - [🧱 Editing the Parser](#-editing-the-parser)\n  - [📦 Packaging](#-packaging)\n  - [🤝 License](#-license)\n\n## 📖 Documentation\n\n### Why Drolta?\n\nDrolta was designed to help game designers work with character data within\nsimulation-driven emergent narrative games. So, simulation games like Crusader Kings,\nCivilization, World Box, Dwarf Fortress, etc. Drolta was developed to help with my\ndissertation research on story sifting. It helps you look for arbitrarily complex\npatterns in characters' social connections or event histories. Drolta is a good\nalternative to SQL.\n\nThe easiest way to demonstrate the benefit of Drolta is with an example. Below are two\nexamples of the same query. This query was used to inspect data\nfrom [Minerva](https://github.com/ShiJbey/minerva), a storyworld simulation that\nsimulates families competing for power and land. The query is intended to find pairs of\ncharacters who are rivals, half-siblings, and serve as the heads of their respective\nfamily/clan. Minerva is all about inter-family conflicts like this.\n\nThe first version of the query is written in Drolta. The second version is written in\npure SQL. Notice the Drolta version's conciseness and how easy it is to understand its\nintentions compared to the SQL version. While the pure SQL version has its benefits,\nDrolta's syntax is more accessible to new users. The query engine performs all necessary joins, variable unification, and filtering. Drolta is not a replacement for SQL, but it makes it easier\nto write queries.\n\n**Drolta Query:**\n\n```plaintext\nFIND\n    ?c0, ?c1\nWHERE\n    family_head(head_id=?c0, end_date=NULL)\n    family_head(head_id=?c1, end_date=NULL)\n    (?c0 != ?c1)\n    relation(character_id=?c0, target_id=?c1, relation_type=\"rival\")\n    relation(character_id=?c1, target_id=?c0, relation_type=\"rival\")\n    half_siblings(character_a=?c0, character_b=?c1)\nLIMIT\n    10;\n```\n\n**SQL Query:**\n\n```sql\nSELECT DISTINCT\n    c0.uid as c0,\n    c1.uid as c1\nFROM\n    characters c0,\n    characters c1,\n    families f0\nWHERE\n (\n    (\n        EXISTS (\n            SELECT\n                1\n            FROM\n                family_heads fh\n            WHERE\n                fh.head = c0.uid\n                AND fh.family = f0.uid\n                AND fh.end_date = NULL\n        )\n        AND EXISTS (\n            SELECT\n                1\n            FROM\n                family_heads fh\n            WHERE\n                fh.head = c1.uid\n                AND fh.family = f0.uid\n                AND fh.end_date = NULL\n        )\n    )\n    AND EXISTS (\n        WITH\n            Rivals AS (\n                SELECT\n                    r1.character_id AS character_a_uid,\n                    r1.target_id AS character_b_uid\n                FROM\n                    relations r1\n                JOIN relations r2 ON\n                (\n                    r1.character_id = r2.target_id\n                    AND r2.target_id = r1.character_id\n                    AND r1.relation_type = \"Rival\"\n                    AND r2.relation_type = \"Rival\"\n                )\n            )\n        SELECT\n            1\n        FROM\n            Rivals\n        WHERE\n            character_a_uid = c0.uid\n            AND character_b_uid = c1.uid\n    )\n    AND EXISTS (\n        WITH\n            HalfSiblings AS (\n                SELECT\n                    s.character_id AS sibling1_uid,\n                    s.sibling_id AS sibling2_uid\n                FROM\n                    siblings s\n                    JOIN characters c1 ON s.character_id = c1.uid\n                    JOIN characters c2 ON s.sibling_id = c2.uid\n                WHERE\n                (\n                    c1.biological_father = c2.biological_father\n                    AND c1.mother != c2.mother\n                )\n                OR (\n                    c1.mother = c2.mother\n                    AND c1.biological_father != c2.biological_father\n                )\n            )\n        SELECT\n            1\n        FROM\n            HalfSiblings\n        WHERE\n            sibling1_uid = c0.uid  AND sibling2_uid = c1.uid\n    )\n) AND c0.uid != c1.uid\nLIMIT\n    10;\n```\n\n### Installation\n\nThis package can be installed from PyPI.\n\n```bash\npip install drolta\n```\n\nYou can test the installation by printing the current drolta version in the Python REPL. Depending on when you install drolta, the version should be greater than or equal to the one printed below.\n\n```bash\n$ python3\n\n\u003e\u003e\u003e import drolta\n\u003e\u003e\u003e drolta.__version__\n0.4.1\n```\n\n### Getting Started\n\nYou can a getting started sample in this Google Colab Notebook. This sample contains data based on characters from HBO's House of the Dragon Series.\n\n[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ShiJbey/drolta_py/blob/main/samples/getting_started.ipynb)\n\nAlternatively, the same sample code can be found [here](./samples/getting_started.py).\n\n### Design Philosophy\n\nDrolta has three design goals:\n\n1. **Readability** - Users should be able to easily understand the intent of a query\n2. **Declarative Syntax** - Queries should be more about what you want to find and less\n   about how to find it.\n3. **Reuse and Composition** - Users should be able to reuse query logic to compose\n   larger queries.\n\nDrolta is not focused on being the most performant query engine. Every abstraction has a\ncost. However, it does aim to be an accessible alternative to raw SQL.\n\n### Queries\n\nQueries are the main focus of Drolta. They enable users to search for information within\nthe database. Drolta's query engine is an alternative to executing queries in raw SQL.\n\n**Example queries:**\n\n```plaintext\nFIND\n    ?siblingId, ?siblingName\nWHERE\n    PaternalHalfSiblings(x=?adam_id, x=?siblingId)\n    character(id=?character_id, name=\"Addam\")\n    character(id=?siblingId, name=?siblingName)\nORDER BY ?siblingId;\n```\n\nQueries have two required parts: a `FIND`-clause and a `WHERE`-clause. It may also have\nadditional statements following the WHERE clause to help with sorting and limiting\noutput size (see [GROUP BY](#group-by), [ORDER BY](#order-by), and [LIMIT](#limit)).\n\nThe `FIND`-clause always goes first and signals the start of the query. The find-clause\ncontains all variables output by the query and their aliases if provided. The output\nvariable aliases differ from [rule/predicate aliases](#tablerule-aliases). They are used to give\nalternate column names to the query output. Otherwise, the column names will match the\nvariable names without the leading '?'.\n\nVariables in drolta are identifiers with a leading '?' (question mark). For example,\n`?character_id`. Variable names may not start with a number and may only contain\nletters, numbers, and underscores.\n\n**Examples of valid variable names:**\n\n```plaintext\n?id\n?family_id\n?name\n?a_b_c_123\n?_x\n```\n\n**Examples of invalid variable names:**\n\n```plaintext\napple\n?\n?123\n?family-id\n?character\u0026house\n```\n\nWhen writing a query, the goal is to find values in the database that hold true across\nall predicates, rules, and filters within the where-clause. This process is\ncalled [variable unification](https://en.wikipedia.org/wiki/Unification_(computer_science)).\nInstead of performing variable assignments, like one would do in a language like Java (\nExample: `int x = 10;`), users specify where variables are used, and the query engine\nensures that results bound to those variables are valid. In this way, Drolta is more\nsimilar to a logic programming language.\n\n### Predicates\n\nPredicates are treated as base facts about the world. Each predicate corresponds\none-to-one with a table in your database. If you would like to change the predicate\nname, use an [alias](#tablerule-aliases). Each column of a table corresponds to one of the\nparameters that can be bound by the predicate. Users cannot create new predicates except\nby creating new database tables. Alternatively, users can define new [rules](#rules) to\ntake advantage of existing predicates.\n\nLet's use the `characters` table from the [Getting Started](#getting-started) sample as\nan example. Inside SQLite, you would have something like the following:\n\n| id  | name     | house     | sex | life_stage | is_alive |\n| --- | -------- | --------- | --- | ---------- | -------- |\n| 1   | Rhaenyra | Targaryen | F   | Adult      | 1        |\n| 2   | Laenor   | Velaryon  | M   | Adult      | 1        |\n| 3   | Harwin   | Strong    | M   | Adult      | 1        |\n| 4   | Jacaerys | Targaryen | M   | Teen       | 1        |\n| ... | ...      | ...       | ... | ...        | ...      |\n\nYou can access this table using the `character` predicate and setting any of the column\nnames equal to a variable or value. For example, the following query uses the predicate\nfor characters in a query to get all characters that belong to House Targaryen. The\npredicate binds their IDs and names to the `?character_id` and `?name` variables while\nalso performing a minor filter on the 'house' column.\n\n```plaintext\nFIND\n    ?character_id,\n    ?name\nWHERE\n    characters(id=?character_id, name=?name, house=\"Targaryen\")\n```\n\n### Rules\n\nRules are used to define reusable queries that are treated like predicates within other\nrules and queries. Rules have two parts: a `DEFINE` clause and a `WHERE` clause. The\n`DEFINE`- clause is where users specify the name of the rule and the variables output by\nthe rule. The `WHERE` clause is the same as with queries. It is where all calls to\npredicates, rules, and filters are placed.\n\nRules are loaded into the query engine by placing them inside a Drolta script and\nloading the script content with the `QueryEngine.execute_script(...)` method.\n\nCurrently, rules may only have one definition (unlike Prolog). Redefining a rule will\noverwrite any pre-existing definition.\n\nBelow is an example of a rule for finding characters in a game who are paternal\nhalf-siblings (they share the father but not the same mother).\n\n```plaintext\nDEFINE\n    PaternalHalfSiblings(?x, ?y)\nWHERE\n    relation(from_id=?x, to_id=?x_bf, type=\"BiologicalFather\")\n    relation(from_id=?y, to_id=?y_bf, type=\"BiologicalFather\")\n    relation(from_id=?x, to_id=?x_m, type=\"Mother\")\n    relation(from_id=?y, to_id=?y_m, type=\"Mother\")\n    (?x_m != ?y_m);\n```\n\n### Table/Rule Aliases\n\nAliases allow users to refer to tables and rules by alternative names. This feature is\nmainly used to create aliases for SQLite table names since Prolog generally uses\nsingular nouns while SQL tables use plural nouns. Since Drolta pulls design inspiration\nfrom Prolog, singular nouns generally read better than plural.\n\nIn the example below, perhaps we have a database of information about non-player\ncharacters in a video game. We want to reference the `characters` database table using\nan alias.\n\nAliases must be defined in a drolta script and loaded into the query engine using the\n`QueryEngine.execute_script(...)` method.\n\n```plaintext\nALIAS characters AS character;\n```\n\nLater in your script, you can use this alias when defining rules.\n\n```plaintext\nALIAS characters AS character;\n\nDEFINE adult(?x) WHERE character(uid=?x, age\u003e18);\n```\n\n### Output Variable Aliases\n\nTo help with readability, output variables for queries and rules may also have aliases. The aliases specified after the variable and may be used with [aggregation functions](#aggregation-functions) to supply more meaningful names. These aliases would then be used when (1) calling a rule within a query or (2) getting the names of the columns in the final query result.\n\nBelow is an example of a rule using an alias for house size. Notice we use the size parameter when calling the rule instead of `?character_id`, and we also alias `house_id` to `id`.\n\n```plaintext\nRULE\n    HouseSize(?house_id AS id, COUNT(?character_id) AS size)\nWHERE\n    characters(id=?character_id, house_id=?house_id)\nGROUP BY ?house_id\nORDER BY ?size DESC;\n\n--\n\nFIND\n    ?house_id, ?house_size\nWHERE\n    HouseSize(id=?house_id, size=?house_size)\n    (?house_size \u003c 4)\n```\n\n### Filter Statements\n\nFilters are how users specify constraints on variable values in the output. For example,\nfilters would help to check if a character is over a given age or if they belong to one\nof a set of noble houses. Drolta supports comparison filters and list membership checks.\nFilters can be chained together with [AND](#and-statements) and [OR](#or-statements) to\ncreate sophisticated constraints.\n\n#### Comparison Filtering\n\nDrolta supports the following comparison operators:\n\n- `=`: Checks if two values are equivalent (Example: `(?age = 32)`)\n- `!=`: Checks if two value are not equivalent (Example: `(?age != 32)`)\n- `\u003c`: Checks if a value is less than another (Example: `(?age \u003c 32)`)\n- `\u003e`: Checks if a value is greater than another (Example: `(?age \u003e 32)`)\n- `\u003c=`: Checks if a value is less than or equal to another (Example: `(?age \u003c= 32)`)\n- `\u003e=`: Checks if a value is greater than or equal to another (Example: `(?age \u003e= 32)`)\n\nThe value to the left of the operator must always be a variable. The value to the right\nof the operator can be a number, text, or another variable.\n\n#### Membership filtering\n\nMembership filtering uses the `IN` keyword to check if a variable's value is within a\ngiven list of values, or `NOT IN` to check if a value is absent from a list. The list cannot contain variables. Also, the data type of all the\nvalues in the list should be the same (all integers, floats, or strings).\n\nThe filter statement below will pass if the `?house` value is equal to any of the house\nnames within the provided list.\n\n```plaintext\n(?house IN [\"Targaryen\", \"Velaryon\", \"Lannister\"])\n```\n\n### NULL Checking\n\nSometimes, you may want to check if a value is missing or NULL within the database. The\n`NULL` keyword can be used within predicates, rules, and filters to check for null\nvalues.\n\nThe following example code shows how to check for null values in a predicate/rule. This\npredicate would bind all characters that do not belong to a house because their house\nvalue is not present.\n\n```plaintext\ncharacter(id=?character_id, house=NULL)\n```\n\nThe following code does the same as the previous example, but it uses a filter instead\nof passing NULL directly to the `character` predicate.\n\n```plaintext\ncharacter(id=?character_id, house=?house)\n(?house = NULL)\n```\n\n### AND-Statements\n\n**Syntax:**\n\n```plaintext\n(\u003cfilter_statement\u003e AND \u003cfilter_statement\u003e)\n```\n\nThe `AND` keyword is used between filter conditions to signal that both filters (on the\nleft and right sides) must hold true for the entire filter to pass.\n\n**Example:**\n\nThe example code below is a query that uses the `AND` keyword to check that characters\nreturned by the query are older than 32 and belong to House Belmont.\n\n```plaintext\nFIND\n    ?character_id, ?age\nWHERE\n    character(id=?character_id, age=?age, house=?house)\n    ((?age \u003e 32) AND (?house = \"Belmont\"));\n```\n\n### OR-Statements\n\n**Syntax:**\n\n```plaintext\n(\u003cfilter_statement\u003e OR \u003cfilter_statement\u003e)\n```\n\nThe `OR` keyword is used between filter conditions to signal that one or both filter\nstatements must hold true for the entire filter to pass.\n\n**Example:**\n\nThe example code below is a query that uses the `OR` keyword to check that characters\nreturned by the query belong to either House Targaryen or House Belmont.\n\n```plaintext\nFIND\n    ?character_id, ?house\nWHERE\n    character(id=?character_id, house=?house)\n    ((?house = \"Targaryen\") OR (?house = \"Belmont\"));\n```\n\n### NOT-Statements\n\n**Syntax for Filters:**\n\n```plaintext\nNOT \u003cfilter_statement\u003e\n```\n\n**Syntax for Predicates/Rules:**\n\n```plaintext\nNOT \u003cpredicate or rule\u003e\n```\n\nThe `NOT` keyword can be used with predicates, rules, and filters. It has different\nsemantics depending on whether it is used with a filter statement versus a\npredicate/rule. When used with a filter statement, it inverts the result of the\ncondition. So, `(NOT (?age \u003e 32))` is equivalent to `(age \u003c= 32)`.\n\nWhen `NOT` is used with a predicate or rule, it causes the predicate/rule to act like a\nfilter, removing any variable values returned by the predicate/rule. Just like filters,\npredicates and rules preceded by `NOT` cannot be the first statement within a WHERE\nclause because they need something to filter.\n\n**Example:**\n\nBelow is an example of `NOT` being used with a rule. In the query, we use `NOT` to\nremove all results from the query where the characters belong to the same house. You can\nassume that we have rules named `FromSameHouse` and `HalfSiblings` that we defined when\ncreating the database.\n\n```plaintext\nFIND\n    ?x, ?y\nWHERE\n    character(id=?x)\n    character(id=?y)\n    HalfSiblings(character_a=?x, character_b=?y)\n    NOT FromSameHouse(character_a=?x, character_b=?y);\n```\n\n### ORDER BY\n\n**Syntax:**\n\n```plaintext\nORDER BY \u003coutput_column\u003e [ASC | DESC] [NULLS FIRST | NULLS LAST], ...\n```\n\n`ORDER BY` is a statement that tells Drolta to order rows according to one of a query's\noutput variables (or its alias). `ORDER BY` is borrowed directly from SQL. It is\noptional but should be placed after the `WHERE` statement. `ORDER BY` can be used in any\norder with `GROUP BY`. Rows can be ordered in ascending order or descending order depending on if ASC or DESC is supplied. By default ASC is used if not specified. Additionally, users can specify if rows with NULL values should come first or last in the ordering using `NULLS FIRST` or `NULLS LAST`.\n\n**Example:**\n\nGet a table of character names and life stages, then order the rows alphabetically by\nname.\n\n```plaintext\nFIND\n    ?name,\n    ?life_stage\nWHERE\n    characters(name=?name, life_stage=?life_stage)\nORDER BY ?name;\n```\n\n### GROUP BY\n\n**Syntax:**\n\n```plaintext\nGROUP BY \u003coutput_column\u003e [, \u003coutput_column_1\u003e, ...]\n```\n\n`GROUP BY` is a statement that tells Drolta to group rows according to one of a query's\noutput variables (or its alias). `GROUP BY` is borrowed directly from SQL. It is\noptional but should be placed after the `WHERE` statement. `GROUP BY` can be used in any\norder with `ORDER BY`. You can specify that items be grouped by more than one column by supplying 2 or more variable names, separated by commas.\n\n**Example:**\n\nGet a table of character names and life stages, then group the rows by life stage (\nChild, Teen, Adult, Senior).\n\n```plaintext\nFIND\n    ?name,\n    ?life_stage\nWHERE\n    characters(name=?name, life_stage=?life_stage)\nGROUP BY ?life_stage;\n```\n\n### LIMIT\n\n**Syntax:**\n\n```plaintext\nLIMIT \u003cinteger\u003e [OFFSET \u003cinteger\u003e]\n```\n\n`LIMIT` tells the query to limit the result to a given number of rows. This should be\nthe last statement in your query if you choose to use it. It is helpful for keeping\noutput sizes small if needed. You can optionally supply an offset to tell the query to return results starting at a given row and counting down. By default the offset is implicitly set to 0 to start at the first row.\n\n**Example:**\n\nGet a table of character names and life stages, and limit the result to the first five\nrows.\n\n```plaintext\nFIND\n    ?name,\n    ?life_stage\nWHERE\n    characters(name=?name, life_stage=?life_stage)\nLIMIT 5;\n```\n\n```plaintext\n-- Query below does the same as the previous, but returns results starting at row 10\n\nFIND\n    ?name,\n    ?life_stage\nWHERE\n    characters(name=?name, life_stage=?life_stage)\nLIMIT 5 OFFSET 10;\n```\n\n### Aggregation Functions\n\nDrolta supports the basic [SQLite aggregation functions](https://www.sqlite.org/lang_aggfunc.html): COUNT, SUM, MIN, MAX, and AVG. In Drolta, they have the same behavior as they would in SQLite. Remember that aggregate functions such as COUNT should be used with GROUP BY. Aggregate functions are called within the FIND-clause of queries or the DEFINE-clause of rules (see examples below). Aggregates results may also be aliased to a different output name using `AS \u003calias\u003e`.\n\n```plaintext\n-- An example query using COUNT to get house IDs mapped to their size.\n\nFIND\n    ?house_id, COUNT(?character_id) AS size\nWHERE\n    characters(id=?character_id, house_id=?house_id)\nGROUP BY ?house_id\nORDER BY ?size DESC;\n```\n\n```plaintext\n-- An example rules using MAX to find the house with the highest reputation.\n\nDEFINE\n    HighestHouseRep(MAX(?rep))\nWHERE\n    houses(reputation=?rep);\n\nDEFINE\n    MostInfluentialHouse(?house_id, ?rep)\nWHERE\n    HighestHouseRep(?rep)\n    houses(id=?house_id, reputation=?rep);\n```\n\n### Comments\n\nDrolta supports line and block comments. Below are examples of both.\n\n```plaintext\n-- This is a line comment\n\n/*\n\nThis is a block comment\nthat spans multiple lines.\n\n*/\n```\n\n## 💢 Troubleshooting\n\n### OperationalError: database is locked\n\nIf you receive this error. Then the cursor of a previous query was not properly closed. This often happens when an exception is thrown within an IPython notebook before the contents of a result are read and destroyed. Restarting the kernel/runtime will remove the problem. Then you should reevaluate your code and ensure you do one of the following.\n\n1. Call `fetch_all()` on the result.\n2. Call `fetch_chunks()` and read all the chunks.\n3. Call `fetch_chunks()`, read some chunks, and call `destroy()` on the result to free resources.\n4. Use the result in a context manager with-statement to have resources automatically destroyed. For example, `with engine.query(...) as result`.\n\n## 🧪 Running Unit Tests\n\nDrolta uses [PyTest](https://docs.pytest.org/en/stable/) for unit testing. All tests are\nin the [/tests](./tests) directory. When contributing features or bug fixes to this\nrepository, please ensure all the tests pass before making a pull request. Thank you.\n\n```bash\n# Step 1: Install dependencies for testing and development (PyTest)\npython -m pip install -e \".[development]\"\n\n# Step 2: Run PyTest\npytest\n```\n\n## 🎨 Syntax Highlighting Support\n\nDrolta is an experimental query language. There are no VSCode extensions to provide\nsyntax highlighting support. If enough people use this Drolta, I'll consider\nimplementing a VSCode extension. Alternatively, if you're interested in implementing it,\nI'd love to hear about it. Please email me.\n\n## 🏆 Ports and Applications\n\nIf you're using drolta or have created a port of drolta for another language. Please\ncontact me to have it listed here. I'd love to have a port for C# in Unity and one for\nGodot.\n\n## 🧱 Editing the Parser\n\nDrolta uses [ANTLR4](https://www.antlr.org/) to generate its parser. If you modify the\n`*.g4` grammar file, you must run the command below. It will generate new base classes\nfor the parser.\n\n```bash\nantlr4 -Dlanguage=Python3 -listener -no-visitor ./src/drolta/Drolta.g4 -o ./src/drolta/parsing\n```\n\n**WARNING:** This can cause breaking changes in the implementation that must be\naddressed before using the package.\n\nYou can visualize an example parse tree with the following command (assuming you have\n`antlr4-tools` installed by pip).\n\n```bash\nantlr4-parse src/drolta/Drolta.g4 prog -gui\n```\n\n## 📦 Packaging\n\nDrolta is packaged using [Hatchling](https://hatch.pypa.io/1.9/). The following command\nwill create build and source distributions.\n\n```bash\n$ python3 -m build\n\ndist/\n├── drolta-\u003cVERSION\u003e-py3-none-any.whl\n└── drolta-\u003cVERSION\u003e.tar.gz\n```\n\n## 🤝 License\n\nThis project is licensed under the [MIT License](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshijbey%2Fdrolta_py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshijbey%2Fdrolta_py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshijbey%2Fdrolta_py/lists"}