{"id":16326464,"url":"https://github.com/blueneogeo/xtend-onyx","last_synced_at":"2026-02-04T00:37:14.382Z","repository":{"id":79280767,"uuid":"84878783","full_name":"blueneogeo/xtend-onyx","owner":"blueneogeo","description":"DSL for querying the Onyx database","archived":false,"fork":false,"pushed_at":"2019-03-17T14:57:12.000Z","size":62,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-14T22:11:53.771Z","etag":null,"topics":["annotations","dsl","metadata","onyx","query"],"latest_commit_sha":null,"homepage":null,"language":"Xtend","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/blueneogeo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2017-03-13T22:01:26.000Z","updated_at":"2019-03-17T14:57:13.000Z","dependencies_parsed_at":"2023-05-24T20:15:35.544Z","dependency_job_id":null,"html_url":"https://github.com/blueneogeo/xtend-onyx","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blueneogeo%2Fxtend-onyx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blueneogeo%2Fxtend-onyx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blueneogeo%2Fxtend-onyx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blueneogeo%2Fxtend-onyx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/blueneogeo","download_url":"https://codeload.github.com/blueneogeo/xtend-onyx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254235711,"owners_count":22036965,"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":["annotations","dsl","metadata","onyx","query"],"created_at":"2024-10-10T23:08:26.964Z","updated_at":"2026-02-04T00:37:09.341Z","avatar_url":"https://github.com/blueneogeo.png","language":"Xtend","funding_links":[],"categories":[],"sub_categories":[],"readme":"xtend-onyx\n==========\n\nThis thin library adds a small xtend DSL for querying the [Onyx Database](https://github.com/OnyxDevTools/onyx-database-parent).\n\nIt has the following features:\n\n- simple query syntax with boolean operators\n- type-safe static compile time query checks\n- since it is static, in an IDE you get code completion for fields\n- and since it is static, if your model changes, your queries break and can be corrected\n- support for joins across your entities\n- observe query changes\n\n# Table of Contents\n\n  * [xtend\\-onyx](#xtend-onyx)\n  * [Table of Contents](#table-of-contents)\n  * [Example](#example)\n  * [Getting Started](#getting-started)\n\t  * [Gradle commands](#gradle-commands)\n\t  * [In your own project](#in-your-own-project)\n\t  * [Setting up your entities](#setting-up-your-entities)\n  * [Querying entities](#querying-entities)\n\t  * [Creating the TypedQuery](#creating-the-typedquery)\n    * [Using where](#using-where)\n    * [Using order](#using-order)\n  * [Getting results](#getting-results)\n    * [As a List](#as-a-list)\n    * [As a lazy List](#as-a-lazy-list)\n    * [A list of selected fields](#a-list-of-selected-fields)\n    * [The first result](#the-first-result)\n    * [Limiting results](#limiting-results)\n  * [Updating entities](#updating-entities)\n    * [Setting new values](#setting-new-values)\n  * [Deleting entities](#deleting-entities)\n  * [Listening for changes](#listening-for-changes)\n\n# Example\n\n```xtend\nimport static extension nl.kii.onyx.OnyxExtensions.*\n\nval db = factory.persistenceManager\n\nval person = new Person =\u003e [\n\tfirstName = 'Foo'\n\tlastName = 'Bar'\n\taddress = new Address =\u003e [\n\t\tstreet = 'FooStreet'\n\t\thouseNr = 99\n\t]\n]\n\ndb.saveEntity(person)\n\nval results = db\n\t.query (Person.Data)\n\t.where [ firstName != null \u0026\u0026 address_houseNr == 99 ]\n\t.order [ -id ]\n\t.list\n\nprintln(results)\n```\n\n# Getting Started\n\n## Gradle commands\n\nThis project uses Gradle. In the project root, type:\n\n- *gradle build* - To build the project and run a test.\n- *gradle eclipse* - To generate the Eclipse projects to import.\n- *gradle idea* - To generate the IntelliJ IDEA projects to import.\n- *gradle install* - To install the library on your local maven repository.\n\n## In your own project\n\nXtend-onyx has only dependencies on the standard xtend java library and the onyx database.\n\nIt is not yet available on Maven Central, so to use it you have to install it to your local repository.\n\nTo import it after you have gradle installed it locally (see above), add this dependency to your own project:\n\n\tcompile \"net.sagittarian.xtend-onyx-core:1.3\"\n\nand add the Onyx database dependency as well, eg:\n\n\tcompile \"com.onyxdevtools:onyx-database:$onyxVersion\"\n\n## Setting up your entities\n\nTo use xtend-onyx, you must annotate your entities with the **@OnyxFields** and **OnyxJoins** Active Annotations. This will make the metadata from your entity statically available to the xtend-onyx library. Every entity you annotate will get a **Data** subclass with metadata selectors you can use when querying.\n\nFor example, say you have a class Person:\n\n```xtend\n@Entity\nclass Person extends IManagedEntity {\n\t@Attribute String firstName\n}\n```\n\nYou will have to add the xtend-onyx annotations to it like this:\n\n```xtend\n@OnyxFields\n@OnyxJoins\n@Entity\nclass Person extends IManagedEntity {\n\t@Attribute String firstName\n}\n```\n\nThis will create the metadata class Person.Data for you. This class will provide you selectors. Person.Data will have this method added:\n\n```xtend\ndef Field\u003cString\u003e firstName()\n```\n\nThese selectors you can then use for querying your entities.\n\n# Querying entities\n\n## Creating the TypedQuery\n\nThese methods can be used in a **TypedQuery**. This Xtend wrapper builds an Onyx Query. This is used in combination with the OnyxExtensions static extensions. This extension class provides the **query(PersistenceManager, MetaData\u003cT\u003e)** method, that creates a **TypedQuery** instance to work with.\n\nFor example:\n\n```xtend\nimport static extension net.sagittarian.onyx.OnyxExtensions.*\n\nval typedQuery = db.query(Person.Data)\n```\n\n*Note: you need to pass the metadata class, not the type you are  querying. In this case, that means you need to pass Person.**Data**, not Person. This is the metadata class that was generated for you by the active annotations you added to your entity.*\n\nThis query can be given commands to filter entities, such as:\n\n\t.select\n\t.where\n\t.order\n\nAfter setting these properties as well as others, you can call .build too generate the Onyx Query. However, unless you want to re-use an Onyx Query object, you will normally directly call one of the following:\n\n\t.list\n\t.lazyList\n\t.first\n\t.update\n\t.delete\n\t.listen\n\nBelow is an explanation of how to use each of the operations the typed query provides.\n\n## Using where\n\nLets you filter the stored entities with query criteria.\n\n```xtend\nquery.where( (Metadata\u003cT\u003e)=\u003eQueryCriteria )\n```\n\nThe metadata instance provides you with the field and join selectors, and the extension provides you with handy overloads that let you create criteria with these selectors. For example:\n\n\tselector == value\n\nThis translates to new QueryCritera(selector.name, EQUALS_TO, value). Similar overloads exist for the following operators: \n\n\t!  not\n\t== equals\n\t!= not equals\n\t\u003e  greater than\n\t\u003e= greater than or equals\n\t\u003c  smaller than\n\t\u003c= smaller than or equals\n\t\u0026\u0026 and\n\t|| or\n\nExample:\n\n```xtend\ndb\n\t.query (Address.Data)\n\t.where [ street == 'Busystreet' \u0026\u0026 houseNr \u003e= 20 ]\n```\n\nYou can use full combined expressions, including braces and negation:\n\n```xtend\ndb\n\t.query (Address.Data)\n\t.where [ \n\t\t!(street == 'Busystreet' || houseNr \u003e= 20) \n\t\t\u0026\u0026 street != null\n\t]\n```\n\nThe correct operator precedence is automatically enforced (this is a feature of the Xtend operators).\n\nIf you use where multiple times on a query, it will perform a logical AND between all of your criteria. This has the same result as the query above:\n\n```xtend\ndb\n\t.query (Address.Data)\n\t.where [ street == 'Busystreet' ]\n\t.where [ houseNr \u003e= 20 ]\n```\n\nThe @OnyxJoin annotation lets you use join selectors in queries. For example, given an **Address** class with a relationship **occupants** of type Person, and each **Person** entity having a **firstName**, you can do this:\n\n```xtend\ndb\n\t.query (Address.Data)\n\t.where [ occuptants_firstName == 'Jason' ]\n```\n\nYou can check if an attribute is one of a list of attributes using selector.in() :\n\n```xtend\n\t// directly specify\n\tquery.where [ name.in('Josh', 'Mary', 'John') ]\n\t\n\t// or as a list:\n\tval wantedPeople = #['Josh', 'Mary', 'John']\n\tquery.where [ name.in(wantedPeople) ]\n\t\n\t// everyone but those people:\n\tquery.where [ name.notIn(wantedPeople) ]\n```\n\nUsing selector.in() can work particulary well with enum types.\n\nThere are also these additional string field operations:\n\n```xtend\n\t// positive expressions\n\tselector.startsWith(text) // field value must start with the text\n\tselector.contains(text) // field value must contain this text\n\tselector.matches(regexp) // field value matches the regular expression\n\n\t// negative versions of the above\n\tselector.notStartsWith(text)text\n\tselector.notContains(text)\n\tselector.notMatches(regexp)\t\n```\n\n## Using order\n\nLets you change the order of the returned entities.\n\n```xtend\n.order( (Metadata\u003cT\u003e)=\u003eQueryOrder … )\n```\n\nThe metadata instance provides you with the field and join selectors, and the extension provides you with handy overloads that let you create QueryOrder instances for these selectors. For example:\n\n\t+selector // order by the selector in increasing order\n\t-selector // order by the selector in decreasing order\n\nYou can call order multiple times, or pass multiple closures.\n\nExamples:\n\n```xtend\ndb\n\t.query (Address.Data)\n\t.where [ street == 'Busystreet' \u0026\u0026 houseNr \u003e= 20 ]\n\t.order [ +street ]\n```\n\nTo sort first by street ascending, and then houseNr descending:\n\n```xtend\ndb\n\t.query (Address.Data)\n\t.where [ street == 'Busystreet' \u0026\u0026 houseNr \u003e= 20 ]\n\t.order [ +street ]\n\t.order [ -houseNr ]\n```\n\nThis has the same result:\n\n```xtend\ndb\n\t.query (Address.Data)\n\t.where [ street == 'Busystreet' \u0026\u0026 houseNr \u003e= 20 ]\n\t.order ( [ +street ], [ -houseNr ] )\n```\n\n# Getting results\n\n## As a List\n\nGet the results of the query as a **List**.\n\n\t.list\n\nExample:\n\n```xtend\nval results = db\n\t.query (Address.Data)\n\t.where [ street == 'Busystreet' \u0026\u0026 houseNr \u003e= 20 ]\n\t.order [ +street ]\n\t.list\nprintln(results) // prints addresses\n```\n\n## As a lazy List\n\nThis performs the same basic function as List, but only gets the matching results and does not yet hydrate them into full entities. The moment you get something from the list, the entity is actually fetched. This can help query large resultsets.\n\nUnlike the Onyx resulted list, this list can be iterated over normally:\n\n```xtend\nval results = db\n\t.query (Address.Data)\n\t.lazyList\n\nfor(result : results) {\n\tprintln(result) // gets and hydrates the result entity\n}\n```\n\n## A list of selected fields\n\nTo tune performance, you can tell Onyx to only fetch the fields you are interested in for each fetched entity.\n\n```xtend\nquery.list( (MetaData)=\u003eField\u003c?\u003e… fieldFns )\n```\n\nFor example, to only fetch the firstName for each entity:\n\n```xtend\nval List\u003cString\u003e results = db\n\t.query (User.Data)\n\t.list [ firstName ]\n```\n\nYou can also select multiple fields, in which case you will get a list of map\u003cfield, value\u003e.\n\n```xtend\nval List\u003cMap\u003cString, ?\u003e results = db\n\t.query (User.Data)\n\t.list( [ firstName ], [ lastName ] )\n```\n\nThese selections also work with lazyList:\n\n```xtend\nval List\u003cMap\u003cString, ?\u003e results = db\n\t.query (User.Data)\n\t.lazyList( [ firstName ], [ lastName ] )\n```\n\nYou can tell Onyx that you only want distinct results, meaning that the combination of values for the fields you selected is unique.\n\n```xtend\nval List\u003cMap\u003cString, ?\u003e results = db\n\t.query (User.Data)\n\t.distinct\n\t.list( [ firstName ], [ lastName ] )\n```\n\nIf in the above example you had two users named ‘Ben Johnson’, only the first match would be returned.\n\n## The first result\n\nTo simply get the first result only, perform .first:\n\n```xtend\nprintln(db.query(Address.Data).first)\n```\n\n## Counting results\n\nGet a count of the amount of entities matching your query with .count:\n\n```xtend\n// how many addresses do we have?\nprintln(db.query(Address.Data).count) \n```\n\n## Limiting results\n\nThere are some properties you can set on the query to set how to page through results:\n\n- **.skip(amount)** : skip [amount] results.\n- **.limit(amount)** : at maximum return [amount] results.\n- **.page(pageNr)** : get a single page of results, given an amount of results per page you set with limit. This can also be combined with skip, the paging will then start after the skipped entities.\n- **.range(first..last)** : return only the [first]th to at most the [last]th result. Also combines with skip and limit, in that it will start counting after the skipped entities, and return at most limit entities.\n\n# Updating entities\n\nYou update entities by calling .set [ ] to tell the query what values to set, and then .update to perform the changes.\n\n## Setting new values\n\nUpdate an entity attribute. Use the =\u003e operator to assign a value inside the closure.\n\n```xtend\ndb\n\t.query (User.Data)\n\t.set [ username =\u003e 'bobby' ]\n\t.update // changes all usernames to bobby\n```\n\nYou can also perform multiple set commands:\n\n```xtend\ndb\n\t.query (User.Data)\n\t.set [ username =\u003e 'bobby' ]\n\t.set [ hobby =\u003e 'knitting' ]\n\t.update\n```\n\nOr combine them in a single set:\n\n```xtend\ndb\n\t.query (User.Data)\n\t.set ( [ username =\u003e 'bobby' ], [ hobby =\u003e 'knitting' ] )\n\t.update\n```\n\nCombine setting with .where [ ] to only change some entities:\n\n```xtend\ndb\n\t.query (User.Data)\n\t.set [ username =\u003e 'bobby' ]\n\t.where [ username == 'robby' ]\n\t.update // changes robby user to bobby\n```\n\nThe update call returns the amount of entities that were updated.\n\n# Deleting entities\n\nEnd a query with .delete to remove all entities that match the query.\n\n```xtend\ndb\n\t.query (User.Data)\n\t.delete // deletes all users\n\ndb\n\t.query (User.Data)\n\t.where [ username == 'robby' ]\n\t.delete // deletes robby\n```\n\nThe delete call returns the amount of entities that were removed.\n\n# Listening for Changes\n\nYou can observe changes as they occur on your database with the **.listen()** method.\nYou pass a QueryListener\u003cT\u003e implementation to listen for any changes that match a\nquery you specify. For example:\n\n```xtend\nval listener = db\n\t.query (User.Data)\n\t.where [ firstName == 'Jones' ]\n\t.listen (new QueryListener\u003cUser\u003e {\n\t\n\t\tonItemAdded(User jones) {\n\t\t}\n\t\t\n\t\tonItemRemoved(User jones) {\n\t\t}\n\t\t\n\t\tonItemUpdated(User jones) {\n\t\t\tprintln('Jones was updated!')\n\t\t}\n\t\n\t})\n\t\n// later:\nlistener.stopListening\n```\n\nNow when a user with firstName Jones was updated, the onItemUpdated method will be called with the user.\n\nWhen you create a listener, you usually want to keep it open for a while to listen for database updates.\nThe listener is attached to the open session. Therefore in order to not leave listeners dangling in memory,\nyou always need to stop them.\n\nWhen you call **.listen()**, you will get back a **Listener**. You stop listening by calling **Listener.stopListening()**.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblueneogeo%2Fxtend-onyx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblueneogeo%2Fxtend-onyx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblueneogeo%2Fxtend-onyx/lists"}