Skip to content

Commit

Permalink
feat: add create table parser (#4)
Browse files Browse the repository at this point in the history
* feat: add constraint parser

* feat: add default value parsing

* feat: complete parse column def

* feat: support table constraints

* feat: add eslint check

* feat: fixing all the lint problem

* fixing while condition
  • Loading branch information
invisal authored Feb 10, 2024
1 parent 28704bd commit f5aa42b
Show file tree
Hide file tree
Showing 20 changed files with 1,804 additions and 793 deletions.
14 changes: 12 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
{
"extends": "next/core-web-vitals",
"extends": ["next/core-web-vitals", "eslint:recommended", "plugin:@typescript-eslint/recommended"],
"rules": {
"@next/next/no-img-element": "off"
}
},
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"overrides": [
{
"files": ["*.test.ts"],
"plugins": ["jest"],
"extends": ["plugin:jest/recommended"],
"rules": { "jest/prefer-expect-assertions": "off" }
}
]
}
1,817 changes: 1,048 additions & 769 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
},
"dependencies": {
"@codemirror/lang-sql": "^6.5.5",
"@lezer/common": "^1.2.1",
"@lezer/lr": "^1.4.0",
"@libsql/hrana-client": "^0.5.5",
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.0.5",
Expand All @@ -35,6 +37,7 @@
"clsx": "^2.1.0",
"cmdk": "^0.2.0",
"dbgate-query-splitter": "^4.9.3",
"eslint-plugin-jest": "^27.6.3",
"lucide-react": "^0.309.0",
"next": "14.0.4",
"react": "^18",
Expand All @@ -50,6 +53,8 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.0.4",
Expand Down
2 changes: 1 addition & 1 deletion src/app/(components)/Cells/GenericCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export default function GenericCell({
onMouseDown={onFocus}
onDoubleClick={onDoubleClick}
>
{(value as any).toString()}
{value.toString()}
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/(components)/Cells/createEditableCell.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DatabaseValue } from "@/drivers/DatabaseDriver";
import { useState, useEffect, FunctionComponent } from "react";
import { useState, useEffect } from "react";
import GenericCell from "./GenericCell";
import styles from "./styles.module.css";

Expand Down
1 change: 0 additions & 1 deletion src/app/(components)/ContentMenuHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
ContextMenuSubContent,
} from "@/components/ui/context-menu";
import { useEffect, useRef, useState } from "react";
import { LucideIcon } from "lucide-react";

function ContextMenuList({ menu }: { menu: OpenContextMenuList }) {
return menu.map((item, menuIndex) => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/(components)/MainScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";
import DatabaseDriver from "@/drivers/DatabaseDriver";
import { useMemo, useEffect, useState, useLayoutEffect } from "react";
import { useMemo, useEffect, useLayoutEffect } from "react";
import DatabaseGui from "./DatabaseGui";
import { DatabaseDriverProvider } from "@/context/DatabaseDriverProvider";
import { TooltipProvider } from "@/components/ui/tooltip";
Expand Down
1 change: 0 additions & 1 deletion src/app/(components)/OptimizeTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ function handleTableCellMouseDown({
y,
x,
ctrl,
shift,
}: {
y: number;
x: number;
Expand Down
2 changes: 1 addition & 1 deletion src/app/(components)/QueryProgressLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function QueryProgressLog({
}: {
progress: MultipleQueryProgress;
}) {
const [currentTime, setCurrentTime] = useState(() => Date.now());
const [, setCurrentTime] = useState(() => Date.now());

useEffect(() => {
if (!progress.error) {
Expand Down
1 change: 0 additions & 1 deletion src/app/(components)/SchemaView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
OpenContextMenuList,
openContextMenuFromEvent,
} from "@/messages/openContextMenu";
import OpacityLoading from "./OpacityLoading";
import { useSchema } from "@/screens/DatabaseScreen/SchemaProvider";

interface SchemaViewItemProps {
Expand Down
2 changes: 0 additions & 2 deletions src/app/(components)/TopNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"use client";
import * as React from "react";
import Link from "next/link";

import { cn } from "@/lib/utils";
import {
NavigationMenu,
Expand Down
52 changes: 48 additions & 4 deletions src/drivers/DatabaseDriver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,57 @@ export interface DatabaseSchemaItem {
export interface DatabaseTableColumn {
name: string;
type: string;
nullable: boolean;
pk: boolean;
pk?: boolean;
constraint?: DatabaseTableColumnConstraint;
}

export type DatabaseColumnConflict =
| "ROLLBACK"
| "ABORT"
| "FAIL"
| "IGNORE"
| "REPLACE";

export interface DatabaseForeignKeyCaluse {
foreignTableName: string;
foreignColumns: string[];
columns?: string[];
}

export interface DatabaseTableColumnConstraint {
name?: string;

primaryKey?: boolean;
primaryColumns?: string[];
primaryKeyOrder?: "ASC" | "DESC";
primaryKeyConflict?: DatabaseColumnConflict;
autoIncrement?: boolean;

notNull?: boolean;
notNullConflict?: DatabaseColumnConflict;

unique?: boolean;
uniqueConflict?: DatabaseColumnConflict;

checkExpression?: string;

defaultValue?: unknown;
defaultExpression?: string;

collate?: string;

generatedExpression?: string;
generatedType?: "STORED" | "VIRTUAL";

foreignKey?: DatabaseForeignKeyCaluse;
}

export interface DatabaseTableSchema {
columns: DatabaseTableColumn[];
pk: string[];
autoIncrement: boolean;
tableName?: string;
constraints?: DatabaseTableColumnConstraint[];
}

export default class DatabaseDriver {
Expand Down Expand Up @@ -87,7 +130,6 @@ export default class DatabaseDriver {
const columns: DatabaseTableColumn[] = result.rows.map((row) => ({
name: row.name?.toString() ?? "",
type: row.type?.toString() ?? "",
nullable: !row.notnull,
pk: !!row.pk,
}));

Expand All @@ -105,7 +147,9 @@ export default class DatabaseDriver {
if (seqRow && Number(seqRow[0] ?? 0) > 0) {
hasAutoIncrement = true;
}
} catch {}
} catch (e) {
console.error(e);
}

return {
columns,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/export-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function exportRowsToSqlInsert(
headers: string[],
records: unknown[][]
): string {
let result: string[] = [];
const result: string[] = [];

const headersPart = headers.map(escapeIdentity).join(", ");

Expand All @@ -35,7 +35,7 @@ function cellToExcelValue(value: unknown) {
}

export function exportRowsToExcel(records: unknown[][]) {
let result: string[] = [];
const result: string[] = [];

for (const record of records) {
const line = record.map(cellToExcelValue).join("\t");
Expand Down
1 change: 1 addition & 0 deletions src/lib/internal-pubsub.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export default class InternalPubSub {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected listeners: Record<string, ((obj: any) => void)[]> = {};

addListener<T = unknown>(channel: string, callback: (obj: T) => void) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/multiple-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function multipleQuery(
}
} catch (e) {
log.end = Date.now();
log.error = (e as any).toString();
log.error = (e as Error).toString();

if (onProgress) {
onProgress({
Expand Down
2 changes: 1 addition & 1 deletion src/lib/sql-execute-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ export async function executePlans(
return { success: true, plans };
} catch (e) {
await driver.query("ROLLBACK");
return { success: false, error: (e as any).toString(), plans: [] };
return { success: false, error: (e as Error).toString(), plans: [] };
}
}
10 changes: 9 additions & 1 deletion src/lib/sql-helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
generateDeleteStatement,
generateInsertStatement,
generateUpdateStatement,
unescapeIdentity,
} from "./sql-helper";

describe("Escape SQL", () => {
Expand All @@ -31,6 +32,11 @@ describe("Escape SQL", () => {
expect(escapeSqlValue(buffer)).toBe(`x'5D41402ABC4B2A76B9719D911017C592'`);
expect(escapeSqlBinary(buffer)).toBe(`x'5D41402ABC4B2A76B9719D911017C592'`);
});

it("unescape identity", () => {
expect(unescapeIdentity(`"users"`)).toBe("users");
expect(unescapeIdentity(`"us""ers"`)).toBe(`us"ers`);
});
});

describe("Generate SQL Statement", () => {
Expand Down Expand Up @@ -120,7 +126,9 @@ describe("Mapping sqlite column type to our table type", () => {
expect(convertSqliteType(type)).toBe(TableColumnDataType.TEXT));
}

expect(convertSqliteType("BLOB")).toBe(TableColumnDataType.BLOB);
it("BLOB column type", () => {
expect(convertSqliteType("BLOB")).toBe(TableColumnDataType.BLOB);
});

const realType = ["REAL", "DOUBLE", "DOUBLE PRECISION", "FLOAT"];
for (const type of realType) {
Expand Down
15 changes: 11 additions & 4 deletions src/lib/sql-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ export function escapeIdentity(str: string) {
return `"${str.replace(/"/g, `""`)}"`;
}

export function unescapeIdentity(str: string) {
let r = str.replace(/^["`]/g, "");
r = r.replace(/["`]$/g, "");
r = r.replace(/""/g, `"`);
return r;
}

export function escapeSqlString(str: string) {
return `'${str.replace(/'/g, `''`)}'`;
}
Expand Down Expand Up @@ -68,8 +75,8 @@ export function generateInsertStatement(
tableName: string,
value: Record<string, unknown>
): string {
let fieldPart: string[] = [];
let valuePart: unknown[] = [];
const fieldPart: string[] = [];
const valuePart: unknown[] = [];

for (const entry of Object.entries(value)) {
fieldPart.push(entry[0]);
Expand All @@ -84,7 +91,7 @@ export function generateDeleteStatement(
tableName: string,
where: Record<string, unknown>
) {
let wherePart: string = Object.entries(where)
const wherePart: string = Object.entries(where)
.map(
([columnName, value]) =>
`${escapeIdentity(columnName)} = ${escapeSqlValue(value)}`
Expand All @@ -105,7 +112,7 @@ export function generateUpdateStatement(
})
.join(", ");

let wherePart: string = Object.entries(where)
const wherePart: string = Object.entries(where)
.map(
([columnName, value]) =>
`${escapeIdentity(columnName)} = ${escapeSqlValue(value)}`
Expand Down
Loading

0 comments on commit f5aa42b

Please sign in to comment.