A GraphQL-inspired markup for querying databases


![Code Climate]()
![Code Climate]()




Contents
--------
- Introduction
- What is DatQL?
- Installing
- Motivation
- Legitimacy
- The Markup
- Queries & Mutations
- Selecting
- Joining
- Inserting & Updating
- Deleting
- API
- Caveats
- Support Table
Introduction
------------
``sql`
SELECT
user_id
FROM text_messages
INNER JOIN (SELECT
users.id AS user_id
FROM users) ON (users.id = text_messages.user_id)) AS users
WHERE (conversation_id = 5)
You can write this:
``
query getUserTextMessages($conversation_id) {
text_messages(conversation_id = $conversation_id) {
...on users(id = text_messages.user_id) {
id[user_id]
}
}
}
DatQL has the super-slick dql package name thanks to @maxogden and can be installed via
NPM or Yarn:
`bash`
$ npm install dql --save-dev
$ yarn add dql@ --dev
Currently, DatQL is only accessible as a CommonJS module that can be used in Node 8+ (maybe earlier, but that's the Babel
preset it uses, sooo..). You must also install v0.2.0 and up, otherwise you'll get the old library, which can now be found here.
It is also important to note that @maxogden happens to (coincidentally) be the a prominent
contributor of the Dat Project, a "distributed data community" that is in no way
related to this project other than just have "dat" in the title.
1. Use object-relational mapping (ORM), such as Laravel or Ruby on Rails,
or even something like SQLAlchemy or Sequelize.
2. Use a query-building library such as Squel, which this library actually uses.
The choices are slim. The biggest issue with ORMs is the fact they usually work best on empty databases,
where all data is inserted and managed by a locally-defined schema consisting of models and controllers. Query builders
are great, but can hard to swap out later on down the line, and the available API is usually attached to a particular
language. For example, I used Squel for all my Node.js database communication, but used
Quill for all my Scala database communication. And in case you're wondering, no, the libraries
are nothing like each other, despite doing virtually the same thing.
So that's where DatQL comes in. DatQL is an abstraction over the abstractions, so to speak. The DatQL markup is parsed
using a publicly-available context-free grammar and can be adapted to virtually any language. While query builders can
be replaced depending on which language the DatQL library supports, the markup remains unchanged, creating an extensible,
open way to write SQL. Plus, DatQL at a glance is much easier to understand than SQL, especially for those who are not SQL
experts.
The Markup
----------
``
query getBookmarksForUser($user_id)
You can also call neighboring mutations or queries as well (where appropriate), such as:
``
users(id = getUserIDFromPage(102)) {
id
}
table, filtering by entries whose ID
matches the user ID provided to the query:`
query getBookmarksForUser($user_id) {
users(id = $user_id) {
id
}
}
`Like GraphQL, you can specify which fields to return inside each table block. You can alias these fields by specifying
an alias in square brackets ([]) next to the field name.
$3
JOINs take on a form similar to those of fragments in GraphQL. While their fundamental philosophies differ, the syntax
is the same. JOIN blocks begin with ...on and must specify a table name and ON clause in parentheticals. Like
tables, JOIN blocks accept field names, and it is strongly advised that you alias all JOIN fields to avoid conflicts.
So, let's wrap up our statement by joining the users table with the bookmarks table:`
query getBookmarksForUser($user_id) {
users(id = $user_id) {
id
...on bookmarks(user_id = users.id, name = "MyBookmark") {
name[bookmark_name]
}
}
}
`It is important to note tht only the first item in the
on parenthetical is considered to be an "ON" statement. Every
subsequent item is considered to belong to the "WHERE" statement. Therefore the above output would look something like
this:`sql
SELECT users.id
FROM users
INNER JOIN (SELECT * name AS bookmark_name
FROM bookmarks WHERE (name = 'MyBookmark'))
AS bookmarks ON (bookmarks.user_id = users.id)
` $3
INSERT and UPDATE statements are both grouped under mutation documents. Whether the resultant query uses INSERT or UPDATE
is dependent on whether a selector is specified for the table. If one is present, the existing row is updated. If one is
not provided, a new row is inserted. For example, to update a user's name:`
mutation getBookmarksForUser($user_id, $user_name) {
users(id = $user_id) {
name: $user_name
}
}
`$3
DELETE statements can only be run on mutations and consist of a single table entry, prefixed with a minus sign (-).
Deletes must contain a selector clause and can only contain join statements (but don't necessarily have to):
`
mutation deleteUser($name) {
- users(name = $name)
}
`API
---
Similar to Apollo's graphql-tag, DatQL uses an ES2015 template literal
tag which is supported by most recent versions of Node. Currently, DatQL supports three SQL flavors: MySQL (mysql), PostgresQL (postgres),
and Microsoft SQL (mssql). The
dql tag processes documents into a tree, returning a function that accepts variables, as well
as the name of the query or mutation to execute. By default, DatQL will always execute the last defined document in a
file. So for our above query:`javascript
const dql = require('dql').postgres;const getBookmarksForUser = dql
;/**
* Outputs {
* text: 'SELECT id, bookmarks.name AS bookmark_name FROM users INNER JOIN (SELECT bookmarks.name, bookmarks.user_id FROM bookmarks) AS bookmarks ON (bookmarks.user_id = users.id) WHERE (id = $1)',
* values: [ 1002 ]
* }
*/
const sql = getBookmarksForUser({
variables: {
user_id: 1002
}
});
// Outputs SELECT id, bookmarks.name AS bookmark_name FROM users INNER JOIN (SELECT bookmarks.name, bookmarks.user_id FROM bookmarks) AS bookmarks ON (bookmarks.user_id = users.id) WHERE (id = 1002)
const sql_str = getBookmarksForUser({
variables: {
user_id: 1002
}
});
`By default, DatQL outputs an object containing both the text of the query and any variables associated with it. This
allows your database engine to sanitize any variables to prevent SQL-injection attacks. To override this behavior,
simply pass
true as the last parameter of the function. If your string contains multiple documents, you can
pass in the name of the entry-point document as the first argument of the function like so:`javascript
getBookmarksForUser('getBookmarksForUser', {
variables: {
user_id: 1002
}
}, true);
`To order your query by a specific field, simply pass in the
orderBy configuration option. By default, all fields are
in ascending order. To switch to descending, set the descending property to true:
`javascript
getBookmarksForUser('getBookmarksForUser', {
variables: {
user_id: 1002
},
orderBy: 'id',
descending: true
}, true);
`Lastly, you can also pass in a
groupBy option to group aggregated results based on certain fields.Caveats
-------
As stated, this library is highly experimental. A couple things to note:
1. The library assumes any fields on the left-side of an operator in any
WHICH/ON statement is a field belonging to
the table in question. As a result, they should not be prefixed by the table name. So for example, the following is correct,
assuming the users table has name field:
`
...on users(name = 'Tyler')
` The following will not work:
`
...on users('Tyler' = users.name)
`2. DatQL is capable of detecting built-in functions, method calls, and variables in
WHICH/ON statements.
Any text that does not match one of these is susceptible to being recognized as a field name. As a result, try to keep
your selectors simple and keep fields to the left of any operator.3. While this library is designed to be an abstraction over SQL, certain database-specific functions such as
Now()` haveSupport Table
-------------
| Selecting (63% completed) | Single Table | ✔ |
| Multiple tables | ✘ | |
| Sub-queries as tables | ✘ | |
| Fields & Aliases | ✔ | |
| Joins | ✔ | |
| Filtering | ✔ | |
| Sorting | ✔ | |
| Grouping | ✔ | |
| Having | ✘ | |
| Limits & Offsets | ✔ | |
| Unions | ✘ | |
| Inserting & Updating (66% completed) | Fields | ✔ |
| Batch operations | ✘ | |
| Filtering | ✔ | |
| Sorting | ✔ | |
| Limits | ✔ | |
| Functions as values | ✘ | |
| Deleting (100% completed) | Joins | ✔ |
| Filtering | ✔ | |
| Sorting | ✔ | |
| Limits | ✔ |