Skip to content

Instantly share code, notes, and snippets.

@kamiazya
Created April 29, 2020 10:00
Show Gist options
  • Save kamiazya/d15de5664a0901356195680bafd9eaac to your computer and use it in GitHub Desktop.
Save kamiazya/d15de5664a0901356195680bafd9eaac to your computer and use it in GitHub Desktop.
import React, { FC } from "react";
import { ITable, INT, Database, VARCHAR, DATE, ENUM } from "./database";
export const CompanyDirectoryDatabase: FC = () => {
const tables: ITable[] = [
{
name: "departments",
columns: {
department_id: new INT({ length: 11, autoIncrement: true }, "事業部ID"),
name: new VARCHAR({ length: 100 }, "名前"),
},
refs: [],
},
{
name: "positions",
columns: {
position_id: new INT(
{ length: 11, autoIncrement: true, notNull: true },
"役職ID"
),
name: new VARCHAR({ length: 100 }, "名前"),
},
refs: [],
},
{
name: "staffs",
columns: {
staff_id: new INT({ autoIncrement: true, notNull: true }, "従業員ID"),
last_name: new VARCHAR({ length: 30, notNull: true }, "苗字"),
first_name: new VARCHAR({ length: 30, notNull: true }, "名前"),
last_name_kana: new VARCHAR({ length: 30, notNull: true }, "苗字カナ"),
first_name_kana: new VARCHAR({ length: 30, notNull: true }, "名前カナ"),
birthday: new DATE({ notNull: true }, "生年月日"),
sex: new ENUM(
["0", "1", "2", "9"],
{ notNull: true },
"性別(ISO 5218)"
),
department_id: new INT({ notNull: true }, "事業部ID"),
position_id: new INT({ notNull: true }, "役職ID"),
hire_date: new DATE({ notNull: true }, "入社日"),
leave_date: new DATE({ default: "NULL" }, "退社日"),
},
refs: [
{
from: "department_id",
to: {
table: "departments",
column: "department_id",
},
},
{
from: "position_id",
to: {
table: "positions",
column: "position_id",
},
},
],
},
];
return <Database name="company_directory" tables={tables} />;
};
import React, { FC } from 'react';
import { Node, Subgraph, DOT, Edge } from '@ts-graphviz/react';
export interface IDatabase {
name: string;
tables: ITable[];
}
export interface ColumnType {
toConstraints(): string;
comment?: string;
}
export namespace mysql {
}
export class INT implements ColumnType {
constructor(
public readonly options?: Readonly<{
length?: number;
autoIncrement?: boolean;
notNull?: boolean;
default?: string;
}>,
public readonly comment?: string,
) {}
public toConstraints(): string {
let type: string | undefined;
let others: string[] = [];
if (this.options) {
if (typeof this.options.length === 'number') {
type = `INT(${this.options.length})`;
} else {
type = 'INT';
}
if (this.options.autoIncrement) {
others.push('AUTO_INCREMENT');
}
if (this.options.notNull) {
others.push('NOT NULL');
}
if (this.options.default !== undefined) {
others.push(`DEFAULT ${this.options.default}`);
}
}
return [type, ...others].filter(v => v !== undefined).join(' ');
}
}
export class VARCHAR implements ColumnType {
constructor(
public readonly options?: Readonly<{
length?: number;
notNull?: boolean;
default?: string;
}>,
public readonly comment?: string
) {}
public toConstraints(): string {
let type: string | undefined;
let others: string[] = [];
if (this.options) {
if (typeof this.options.length === "number") {
type = `VARCHAR(${this.options.length})`;
} else {
type = "VARCHAR";
}
if (this.options.notNull) {
others.push("NOT NULL");
}
if (this.options.default !== undefined) {
others.push(`DEFAULT ${this.options.default}`);
}
}
return [type, ...others]
.filter((v) => v !== undefined)
.join(" ");
}
}
export class DATE implements ColumnType {
constructor(
public readonly options?: Readonly<{
notNull?: boolean;
default?: string;
}>,
public readonly comment?: string
) {}
public toConstraints(): string {
let type = 'DATE';
let others: string[] = [];
if (this.options) {
if (this.options.notNull) {
others.push("NOT NULL");
}
if (this.options.default !== undefined) {
others.push(`DEFAULT ${this.options.default}`);
}
}
return [type, ...others]
.filter((v) => v !== undefined)
.join(" ");
}
}
export class ENUM implements ColumnType {
constructor(
public readonly elements: string[],
public readonly options?: Readonly<{
notNull?: boolean;
}>,
public readonly comment?: string
) {}
public toConstraints(): string {
let type = "ENUM(" + this.elements.join(", ") + ")";
let others: string[] = [];
if (this.options) {
if (this.options.notNull) {
others.push("NOT NULL");
}
}
return [type, ...others]
.filter((v) => v !== undefined)
.join(" ");
}
}
export interface ITable {
name: string;
columns: {
[name: string]: ColumnType;
};
refs: {
from: string;
to: {
table: string;
column: string;
};
}[];
};
export const Database: FC<IDatabase> = (props) => {
return (
<Subgraph
id={`cluster_${props.name}`}
label={props.name}
node={{
shape: "none",
}}
>
{props.tables.map((table) => (
<Table key={table.name} database={props.name} {...table} />
))}
{props.tables.map((table) =>
table.refs.map((ref, i) => (
<Edge
key={`${table.name}ref${i}`}
dir="back"
targets={[
`${ref.to.table}:${ref.to.column}_out`,
`${table.name}:${ref.from}_in`,
]}
/>
))
)}
</Subgraph>
);
};
export const Table: FC<ITable & { database: string }> = (props) => {
return (
<Node
id={props.name}
label={
<DOT.TABLE>
<DOT.TR>
<DOT.TD>
<DOT.B>Name</DOT.B>
</DOT.TD>
<DOT.TD>
<DOT.B>Constraints</DOT.B>
</DOT.TD>
<DOT.TD />
</DOT.TR>
<DOT.HR />
{Object.entries(props.columns).map(([column, type]) => (
<DOT.TR key={column}>
<DOT.TD PORT={column + "_in"}>
<DOT.B>{column}</DOT.B>
</DOT.TD>
<DOT.TD>{type.toConstraints()}</DOT.TD>
<DOT.TD PORT={column + "_out"} />
</DOT.TR>
))}
</DOT.TABLE>
}
/>
);
}
import React from 'react';
import { renderToDot, Digraph } from "@ts-graphviz/react";
import { CompanyDirectoryDatabase } from './company_directory';
const dot = renderToDot(
<Digraph dpi="150">
<CompanyDirectoryDatabase />
</Digraph>
);
console.log(dot);
{
"name": "database-er",
"version": "1.0.0",
"main": "index.js",
"private": true,
"license": "MIT",
"scripts": {
"start": "ts-node src/main.tsx",
"start:dot": "ts-node src/main.tsx > result.dot",
"start:svg": "ts-node src/main.tsx | dot -Tsvg -o result.svg",
"start:png": "ts-node src/main.tsx | dot -Tpng -o result.png"
},
"dependencies": {
"@ts-graphviz/react": "^0.3.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"ts-graphviz": "^0.10.0"
},
"devDependencies": {
"@types/react": "^16.9.34",
"typescript": "^3.8.3"
}
}
@kamiazya
Copy link
Author

result

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment