Skip to content
Oxford Harrison edited this page Nov 15, 2024 · 11 revisions

DOCSSpecifications


⚡️ The schema.js spec.

Manage your entire database structure declaratively here. Add objects, remove object, modify existing objects in-place, and Linked QL's perfect diffing process will diff your new structure with your active DB structure and compose the relevant SQL that executes against your database. (See ➞ Migrations.)

This specification is your guide!

Spec

Below, we start with plain JSON examples in each case, then the relevant spec.

Jump to: DatabaseSchema, TableSchema, ColumnSchema, ConstraintSchema, IndexSchema

DatabaseSchema

{
    // Database name - required
    "name": "database_1",
    // List of tables - required, even if empty
    "tables": []
}
└ Spec
interface DatabaseSchema {
    name: string;
    tables: TableSchema[];
}

TableSchema

{
    // Table name - required
    "name": "users",
    // List of columns - required, even if empty
    "columns": [],
    // List of constraints
    "constraints": [],
    // List of indexes
    "indexes": []
}
└ Spec
interface TableSchema {
    name: string;
    columns: ColumnSchema[];
    constraints: TableConstraintSchemaType[];
    indexes: IndexSchema[];
}

ColumnSchema

{
    // Column name - required
    "name": "id",
    // Column type - required
    "type": "int",
    // PRIMARY_KEY constraint
    "primaryKey": true,
    // IDENTITY constraint
    "identity": true
}
{
    // Column name - required
    "name": "parent",
    // Column type - required
    "type": ["int", 3],
    // NOT_NULL constraint
    "notNull": true,
    // FOREIGN_KEY constraint
    "foreignKey": {
        // Target table - required
        "targetTable": "users",
        // Target columns - required
        "targetColumns": ["id"],
        // Match rule
        "matchRule": "full",
        // Update rule
        "updateRule": "cascade",
        // Delete rule
        "deleteRule": "restrict"
    }
}
└ Spec
interface ColumnSchema {
    name: string;
    type: string | array;
    primaryKey?: boolean | PrimaryKeySchema;
    foreignKey?: ForeignKeySchema;
    uniqueKey?: boolean | UniqueKeySchema;
    check?: string | CheckConstraintSchema;
    default?: string | DefaultConstraintSchema;
    expression?: string | ExpressionConstraintSchema;
    identity: boolean | IdentityConstraintSchema;
    onUpdate?: string | OnUpdateConstraintSchema; // (MySQL-specific attribute)
    autoIncrement?: boolean; // (MySQL-specific attribute)
    notNull?: boolean;
    null?: boolean;
}

ConstraintSchema

{
    // Constraint type - required
    "type": "PRIMARY_KEY",
    // Columns - required
    "columns": ["id_2"],
}
{
    // Constraint type - required
    "type": "FOREIGN_KEY",
    // Columns - required
    "columns": ["parent_2"],
    // Target table - required
    "targetTable": "users",
    // Target columns - required
    "targetColumns": ["id"],
    // Match rule
    "matchRule": "full",
    // Update rule
    "updateRule": "cascade",
    // Delete rule
    "deleteRule": "restrict"
}
{
    // Constraint type - required
    "type": "UNIQUE_KEY",
    // Columns - required
    "columns": ["parent", "full_name"]
}
{
    // Constraint type - required
    "type": "CHECK",
    // Expr - required
    "expr": { "matches": ["email", "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"] }
}
└ Spec
type TableConstraintSchemaType = TablePrimaryKeySchema | TableForeignKeySchema | TableUniqueKeySchema | TableCheckConstraintSchema;
interface TablePrimaryKeySchema extends PrimaryKeySchema {
    type: 'PRIMARY_KEY';
    columns: string[];
}

interface TableForeignKeySchema extends ForeignKeySchema {
    type: 'FOREIGN_KEY';
    columns: string[];
}

interface TableUniqueKeySchema extends UniqueKeySchema {
    type: 'UNIQUE_KEY';
    columns: string[];
}

interface TableCheckConstraintSchema extends CheckConstraintSchema {
    type: 'CHECK';
}
type ColumnConstraintSchemaType = PrimaryKeySchema | ForeignKeySchema | UniqueKeySchema | CheckConstraintSchema | DefaultConstraintSchema | ExpressionConstraintSchema | IdentityConstraintSchema | OnUpdateConstraintSchema;
interface PrimaryKeySchema {
    name: string;
}

interface ForeignKeySchema {
    targetTable: string | string[];
    targetColumns: string[];
    matchRule?: string;
    updateRule?: string | { rule: string, columns: string[] };
    deleteRule?: string | { rule: string, columns: string[] };
    name?: string;
}

interface UniqueKeySchema {
    name: string;
}

interface CheckConstraintSchema {
    expr: Expr;
    name?: string;
}

interface DefaultConstraintSchema {
    expr: Expr;
}

interface ExpressionConstraintSchema {
    expr: Expr;
    stored: boolean;
}

interface IdentityConstraintSchema {
    always: boolean;
}

interface OnUpdateConstraintSchema {
    expr: Expr;
}

IndexSchema

{
    // Index type - required
    "type": "FULLTEXT",
    // Columns - required
    "columns": ["full_name"]
}
└ Spec
interface IndexSchema {
    type: string;
    columns: string[];
    name?: string;
}

Renaming Rules

To rename a database or table or column or constraint or index, use a temporary attribute (called a diff tag) to specify the new name: $name, while leaving the old name in place:

{
    "name": "database_1",
    "$name": "new_database_1"
}

Old names are needed in-place as a means of identification when diffing against your active DB schema. The temporary diff tag ($name) is automatically removed after new name has been picked up by Linked QL during migration.

Example:

[
    {
        "name": "database_1",
        "tables": [
            {
                "name": "users",
                "columns": [
                    {
                        "name": "id",
                        "type": "int",
                        "primaryKey": true,
                        "identity": true
                    },
                    {
                        "name": "first_name",
                        "type": ["varchar", 101]
                    },
                    {
                        "name": "last_name",
                        "type": ["varchar", 101]
                    },
                    {
                        "name": "full_name",
                        "type": ["varchar", 101],
                        "expression": { "join": ["first_name", " ", "last_name"]}
                    },
                    {
                        "name": "email",
                        "type": ["varchar", 50],
                        "uniqueKey": true,
                        "notNull": true,
                        "check": { "expr": { "matches": ["email", "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"] } }
                    },
                    {
                        "name": "parent",
                        "type": "int",
                        "notNull": true,
                        "foreignKey": {
                            "targetTable": "users",
                            "targetColumns": ["id"],
                            "matchRule": "full",
                            "updateRule": "cascade",
                            "deleteRule": "restrict"
                        }
                    }
                ],
                "constraints": [
                    {
                        "type": "PRIMARY_KEY",
                        "columns": ["id_2"],
                    },
                    {
                        "type": "FOREIGN_KEY",
                        "columns": ["parent_2"],
                        "targetTable": "users",
                        "targetColumns": ["id"],
                        "matchRule": "full",
                        "updateRule": "cascade",
                        "deleteRule": "restrict"
                    },
                    {
                        "type": "UNIQUE_KEY",
                        "name": "constraint_name",
                        "columns": ["parent", "full_name"]
                    },
                    {
                        "type": "CHECK",
                        "expr": { "matches": ["email", "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"] }
                    }
                ],
                "indexes": [
                    {
                        "type": "FULLTEXT",
                        "columns": ["full_name"]
                    },
                    {
                        "type": "SPATIAL",
                        "columns": ["full_name"]
                    }
                ]
            }
        ]
    },
    {
        "name": "database_2",
        "tables": []
    }
]
Clone this wiki locally