@linked-db/linked-ql
Advanced tools
Comparing version 0.8.8 to 0.8.9
@@ -14,3 +14,3 @@ { | ||
"icon": "https://webqit.io/icon.svg", | ||
"version": "0.8.8", | ||
"version": "0.8.9", | ||
"license": "MIT", | ||
@@ -17,0 +17,0 @@ "repository": { |
343
README.md
@@ -138,3 +138,3 @@ # Linked QL | ||
Other APIs are covered right in [The Linked QL API](#linked-ql-api) section. You'll find that, in addition to writing pure SQL, you can also programmatically compose queries if you want; an example being the `client.createDatabase()` API for a `CREATE DATABASE` statement. | ||
Other APIs are covered right in [The Linked QL API](https://github.com/linked-db/linked-ql/wiki/API) section. You'll find that, in addition to running pure SQL using `client.query()`, you can also programmatically compose queries if you want; an example being the `client.createDatabase()` API for a `CREATE DATABASE` statement. | ||
@@ -197,5 +197,5 @@ ## Introducing Magic Paths | ||
✨ PRO: *About 50% code, and whole namespacing exercise, now eliminated! And the wild part? No questions asked about your schemas; and no such thing as some upfront relational mapping; we save that for another day!* | ||
✨ _Now, that translates to: about 50% code, and whole namespacing exercise, having been eliminated! And yet: no questions asked about your schemas; no such thing as the usual upfront relationship mapping!_ | ||
Taking things further, multi-level relationships also get a new language: multi-level paths: | ||
Taking things further, multi-level relationships also get a corresponding pattern: multi-level paths: | ||
@@ -208,3 +208,3 @@ ```sql | ||
and in whatever direction the relationship goes (one-to-many, many-to-one, many-to-many), there's a perfect path expression: | ||
and for when you need to model the different forms of relationships out there (one-to-many, many-to-one, many-to-many), path operators that go in any direction: | ||
@@ -217,6 +217,4 @@ ```sql | ||
*(Now pivot tables/junction tables/link tables get an easier way!)* | ||
And it turns out, you can practically have the new magic together with the old craft: | ||
And what's more? You can practically have the new magic together with the old craft: | ||
```sql | ||
@@ -231,4 +229,6 @@ -- Linked QL | ||
We think this will make a lot of your tooling around SQL obsolete and your codebase saner! We're designing this to give you back SQL, and then, a simple, direct upgrade path to "magic mode" *on top of that*! | ||
This means: game on with the regular JOINs for whatever calls for them; take the "magic path" option for whatever doesn't benefit otherwise! | ||
We think this will make a lot of the tooling and manual work around SQL obsolete and your codebase saner! And notice how this gives you back SQL - and every other thing as only an extension of that! | ||
## Introducing Auto-Versioning | ||
@@ -267,5 +267,5 @@ | ||
Meet Linked QL's little addition to your database that does exactly that and lets you alter your DB carefree, but in the safety net of some behind-the-scenes magic that snapshots your schema before each alteration! Meet Automatic Schema Savepoints and Rollbacks! | ||
Meet Linked QL's little addition to your database that does exactly that and lets you alter your DB carefree, but in the safety net of some behind-the-scenes magic that snapshots the relevant schemas before each alteration! Meet Automatic Schema Savepoints and Rollbacks! | ||
You: | ||
You alter your schema and get back a reference to the savepoint automatically created for you: | ||
@@ -279,16 +279,18 @@ ```js | ||
Linked QL: | ||
```js | ||
// A savepoint automatically created for you | ||
// As an axample of what you will see: | ||
console.log(savepoint.description); // Create users table | ||
console.log(savepoint.versionTag); // 1 | ||
console.log(savepoint.savepointDate); // 2024-07-17T22:40:56.786Z | ||
// Or to see everything: | ||
console.table(savepoint.toJson()); | ||
``` | ||
*(More details in the [Savepoint](#the-savepoint-api) API.)* | ||
And you're able to access the same savepoint on-demand using the [`database.savepoint()`](https://github.com/linked-db/linked-ql/wiki/API#databasesavepoint) API: | ||
✨ PRO: *Whole engineering work now essentially moved to the database where they rightly belong! And the fun part? Zero configurations! Zero conventions! Zero costs!* | ||
```js | ||
const savepoint = await client.database('public').savepoint(); | ||
``` | ||
Taking that further, you get a nifty rollback button, should you want to: | ||
Either way, you get a nifty rollback button, should you want to: | ||
@@ -300,3 +302,3 @@ ```js | ||
and you can go many levels back: | ||
all the way back to a point in time, should you want to: | ||
@@ -311,4 +313,6 @@ ```js | ||
and you can "undo" a rollback, or in other words, roll forward to a point in time: | ||
✨ _Now, that translates to: all the engineering work you once did manaually now having been moved to the database! Plus, your schema histories now having been encoded **as data** (**instead of as files**), making them queryable, analyzable, and even visualizable, as regular data!_ | ||
Taking that further, you also get a way to *roll forward* from a rollback! (Much like hitting "Redo" to reverse a certain "Undo"). This time, you simply specify a "forward" movement from your current point in time: | ||
```js | ||
@@ -320,6 +324,4 @@ // "Undo" the last rollback (Gets the users table re-created) | ||
Now this means, *time travel* in any direction! You essentially are able to go *back in time* or *forward in time* in as seamlessly as you move on a movie track! We're building this to fix the broken iterations experience that structured data creates! | ||
giving you *time travel* in any direction! You essentially are able to go back in time or forward in time in as seamlessly as you move on a movie track! | ||
As an additional perk, you also now get to have your schema histories encoded *as data* (instead of *as files*)! Query them; analize them; visualize them as you would any other form of data; or even sync those changes in realtime with the different aspects of your application development and delivery process! | ||
## Re-Introducing Schema-as-Code with `schema.json` | ||
@@ -336,8 +338,12 @@ | ||
{ | ||
// string | ||
"name": "database_1", | ||
"tables": [] // Table objects | ||
// TableSchemaSpec[] | ||
"tables": [] | ||
}, | ||
{ | ||
// string | ||
"name": "database_2", | ||
"tables": [] // Table objects | ||
// TableSchemaSpec[] | ||
"tables": [] | ||
} | ||
@@ -347,4 +353,150 @@ ] | ||
> <details><summary>See the database schema spec</summary> | ||
> <details><summary>See a full example</summary> | ||
> | ||
> ```js | ||
> [ | ||
> { | ||
> // string - required | ||
> "name": "database_1", | ||
> // TableSchemaSpec[] | ||
> "tables": [ | ||
> { | ||
> // string - required | ||
> "name": "users", | ||
> // ColumnSchemaSpec[] - required | ||
> "columns": [ | ||
> { | ||
> // string - required | ||
> "name": "id", | ||
> // string or array like ["int",3] - required | ||
> "type": "int", | ||
> // boolean or PrimaryKeySchemaSpec | ||
> "primaryKey": true, | ||
> // boolean or IdentityConstraintSchemaSpec | ||
> "identity": true | ||
> }, | ||
> { | ||
> // string - required | ||
> "name": "first_name", | ||
> // array or string like "varchar" - required | ||
> "type": ["varchar", 101] | ||
> }, | ||
> { | ||
> // string - required | ||
> "name": "last_name", | ||
> // array or string like "varchar" - required | ||
> "type": ["varchar", 101] | ||
> }, | ||
> { | ||
> // string - required | ||
> "name": "full_name", | ||
> // array or string like "varchar" - required | ||
> "type": ["varchar", 101], | ||
> // string or ExpressionConstraintSchemaSpec | ||
> "expression": "(first_name || ' ' || last_name)" | ||
> }, | ||
> { | ||
> // string - required | ||
> "name": "email", | ||
> // array or string like "varchar" - required | ||
> "type": ["varchar", 50], | ||
> // boolean or UniqueKeySchemaSpec | ||
> "uniqueKey": true, | ||
> // boolean | ||
> "notNull": true, | ||
> // string or CheckConstraintSchemaSpec | ||
> "check": "(email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')" | ||
> }, | ||
> { | ||
> // string - required | ||
> "name": "parent", | ||
> // string or array like ["int",3] - required | ||
> "type": "int", | ||
> // boolean | ||
> "notNull": true, | ||
> // ForeignKeySchemaSpec | ||
> "references": { | ||
> // string or string[] like ["database_2", "users"] - required | ||
> "targetTable": "users", | ||
> // string[] - required | ||
> "targetColumns": ["id"], | ||
> // string | ||
> "matchRull": "full", | ||
> // string or object like { rule: "cascade", columns: ["col1"] } | ||
> "updateRule": "cascade", | ||
> // string or object like { rule: "restrict", columns: ["col1"] } | ||
> "deleteRule": "restrict" | ||
> } | ||
> } | ||
> ], | ||
> // TableConstraintSchemaType[] | ||
> "constraints": [ | ||
> { | ||
> // string - required | ||
> "type": "PRIMARY_KEY", | ||
> // string[] - required | ||
> "columns": ["id"], | ||
> }, | ||
> { | ||
> // string - required | ||
> "type": "FOREIGN_KEY", | ||
> // string[] - required | ||
> "columns": ["parent_2"], | ||
> // string or string[] like ["database_2", "users"] - required | ||
> "targetTable": "users", | ||
> // string[] - required | ||
> "targetColumns": ["id"], | ||
> // string | ||
> "matchRull": "full", | ||
> // string or object like { rule: "cascade", columns: ["col1"] } | ||
> "updateRule": "cascade", | ||
> // string or object like { rule: "restrict", columns: ["col1"] } | ||
> "deleteRule": "restrict" | ||
> }, | ||
> { | ||
> // string - required | ||
> "type": "UNIQUE_KEY", | ||
> // string | ||
> "name": "constraint_name", | ||
> // string[] - required | ||
> "columns": ["parent", "full_name"] | ||
> }, | ||
> { | ||
> // string - required | ||
> "type": "CHECK", | ||
> // string - required | ||
> "expr": "(email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')" | ||
> } | ||
> ], | ||
> // IndexSchemaSpec[] | ||
> "indexes": [ | ||
> { | ||
> // string - required | ||
> "type": "FULLTEXT", | ||
> // string[] - required | ||
> "columns": ["full_name"] | ||
> }, | ||
> { | ||
> // string - required | ||
> "type": "SPATIAL", | ||
> // string[] - required | ||
> "columns": ["full_name"] | ||
> } | ||
> ] | ||
> } | ||
> ] | ||
> }, | ||
> { | ||
> // string - required | ||
> "name": "database_2", | ||
> // TableSchemaSpec[] | ||
> "tables": [] | ||
> } | ||
> ] | ||
> ``` | ||
> | ||
> </details> | ||
> <details><summary>See the schema spec</summary> | ||
> | ||
> ```ts | ||
@@ -357,21 +509,2 @@ > interface DatabaseSchemaSpec { | ||
> | ||
> </details> | ||
<details><summary>Explore the structure further</summary> | ||
------------- | ||
└ *Table schema example:* | ||
```js | ||
{ | ||
"name": "users", // or something like ['db1', 'tbl1'] which would translate to db1.tbl1 | ||
"columns": [], // Column objects (minimum of 1) | ||
"constraints": [], // Constraint objects | ||
"indexes": [] // Index objects | ||
} | ||
``` | ||
> <details><summary>See the table schema spec</summary> | ||
> | ||
> ```ts | ||
@@ -386,52 +519,2 @@ > interface TableSchemaSpec { | ||
> | ||
> </details> | ||
------------- | ||
└ *Column schema examples:* | ||
```js | ||
{ | ||
"name": "id", | ||
"type": "int", | ||
"primaryKey": true, | ||
"identity": true | ||
} | ||
``` | ||
```js | ||
{ | ||
"name": "full_name", | ||
"type": ["varchar", 101], | ||
"generated": "(first_name || ' ' || last_name)" | ||
} | ||
``` | ||
```js | ||
{ | ||
"name": "email", | ||
"type": ["varchar", "50"], | ||
"uniqueKey": true, | ||
"notNull": true, | ||
"check": "(email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')" | ||
} | ||
``` | ||
```js | ||
{ | ||
"name": "parent", | ||
"type": "int", | ||
"notNull": true, | ||
"references": { | ||
"targetTable": "users", | ||
"targetColumns": ["id"], | ||
"matchRull": "full", | ||
"updateRule": "cascade", | ||
"deleteRule": "restrict" | ||
} | ||
} | ||
``` | ||
> <details><summary>See the column schema spec</summary> | ||
> | ||
> ```ts | ||
@@ -455,44 +538,2 @@ > interface ColumnSchemaSpec { | ||
> | ||
> </details> | ||
--------------- | ||
└ *Table constraint examples:* | ||
```js | ||
{ | ||
"type": "PRIMARY_KEY", | ||
"name": "constraint_name", | ||
"columns": ["id"], | ||
} | ||
``` | ||
```js | ||
{ | ||
"type": "UNIQUE_KEY", | ||
"columns": ["email"] | ||
} | ||
``` | ||
```js | ||
{ | ||
"type": "FOREIGN_KEY", | ||
"columns": ["parent"], | ||
"targetTable": "users", // or something like ['db1', 'tbl1'] which would translate to db1.tbl1 | ||
"targetColumns": ["id"], | ||
"matchRull": "full", | ||
"updateRule": "cascade", | ||
"deleteRule": { rule: "restrict", "columns": ["col1", "col2"] } | ||
} | ||
``` | ||
```js | ||
{ | ||
"type": "CHECK", | ||
"expr": "(email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')" | ||
} | ||
``` | ||
> <details><summary>See the table constraint schema spec</summary> | ||
> | ||
> ```ts | ||
@@ -523,6 +564,2 @@ > type TableConstraintSchemaType = TablePrimaryKeySchemaSpec | TableForeignKeySchemaSpec | TableUniqueKeySchemaSpec | TableCheckConstraintSchemaSpec; | ||
> | ||
> </details> | ||
> <details><summary>See the column constraint schema spec</summary> | ||
> | ||
> ```ts | ||
@@ -573,24 +610,2 @@ > type ColumnConstraintSchemaType = PrimaryKeySchemaSpec | ForeignKeySchemaSpec | UniqueKeySchemaSpec | CheckConstraintSchemaSpec | DefaultConstraintSchemaSpec | ExpressionConstraintSchemaSpec | IdentityConstraintSchemaSpec | OnUpdateConstraintSchemaSpec; | ||
> | ||
> </details> | ||
------------- | ||
└ *Index schema examples:* | ||
```js | ||
{ | ||
"type": "FULLTEXT", | ||
"columns": ["full_name"] | ||
} | ||
``` | ||
```js | ||
{ | ||
"type": "SPATIAL", | ||
"columns": ["full_name"] | ||
} | ||
``` | ||
> <details><summary>See the index schema spec</summary> | ||
> | ||
> ```ts | ||
@@ -603,7 +618,5 @@ > interface IndexSchemaSpec { | ||
> ``` | ||
> | ||
> | ||
> </details> | ||
</details> | ||
Now, if you had that somewhere in your application, say at `./database/schema.json`, Linked QL could help keep it in sync both ways with your database: | ||
@@ -614,3 +627,3 @@ | ||
🐥 You also get to see a version number on each database object in your schema essentially incrementing on each migrate operation (whether by you or by colleague), and decrementing on each rollback operation (whether by you or by colleague). | ||
⚡️ You also get to see a version number on each database object in your schema essentially incrementing on each migrate operation (whether by you or by colleague), and decrementing on each rollback operation (whether by you or by colleague). | ||
@@ -617,0 +630,0 @@ Thanks to a DB-native schema version control system, no need to maintain past states, or risk losing them; the DB now becomes the absolute source of truth for both itself and its client applications, as against the other way around. (You may want to see how that brings us to [true "Schema as Code" in practice](#test-heading).) |
5308065
690