diff --git a/src/app/api/events/route.ts b/src/app/api/events/route.ts index 6a201c31..bd9122b0 100644 --- a/src/app/api/events/route.ts +++ b/src/app/api/events/route.ts @@ -5,7 +5,7 @@ // All recorded data will be stored in the Starbase Database. import { headers } from "next/headers"; -import { after, NextRequest, NextResponse } from "next/server"; +import { NextRequest, NextResponse } from "next/server"; import zod from "zod"; import { insertTrackingRecord } from "./insert-tracking-record"; @@ -55,11 +55,7 @@ export const POST = async (req: NextRequest) => { } // Save the event - after(() => { - insertTrackingRecord(deviceId, validate.data.events.slice(0, 50)) - .then() - .catch(); - }); + await insertTrackingRecord(deviceId, validate.data.events.slice(0, 50)); return NextResponse.json( { diff --git a/src/components/board/board-chart-editor.tsx b/src/components/board/board-chart-editor.tsx index 3c0274c5..f5b66491 100644 --- a/src/components/board/board-chart-editor.tsx +++ b/src/components/board/board-chart-editor.tsx @@ -12,7 +12,7 @@ import { ChartValue } from "../chart/chart-type"; import EditChartMenu from "../chart/edit-chart-menu"; import ResultTable from "../gui/query-result-table"; import SqlEditor from "../gui/sql-editor"; -import OptimizeTableState from "../gui/table-optimized/OptimizeTableState"; +import OptimizeTableState from "../gui/table-optimized/optimize-table-state"; import { Button } from "../orbit/button"; import { MenuBar } from "../orbit/menu-bar"; import { createAutoBoardChartValue } from "./board-auto-value"; diff --git a/src/components/gui/aggregate-result/aggregate-result-button.tsx b/src/components/gui/aggregate-result/aggregate-result-button.tsx index 4bd6cd47..e9988dfc 100644 --- a/src/components/gui/aggregate-result/aggregate-result-button.tsx +++ b/src/components/gui/aggregate-result/aggregate-result-button.tsx @@ -1,11 +1,11 @@ +import { LucideCheck, LucideChevronDown } from "lucide-react"; +import { useCallback, useEffect, useState } from "react"; import { buttonVariants } from "../../ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover"; +import ListButtonItem from "../list-button-item"; import OptimizeTableState, { AggregateFunction, -} from "../table-optimized/OptimizeTableState"; -import { useCallback, useEffect, useState } from "react"; -import ListButtonItem from "../list-button-item"; -import { LucideCheck, LucideChevronDown } from "lucide-react"; +} from "../table-optimized/optimize-table-state"; export interface AggregateResult { sum: number | string | undefined; avg: number | string | undefined; @@ -74,7 +74,7 @@ export default function AggregateResultButton({ {displayResult}{" "} - {!!displayResult && } + {!!displayResult && } diff --git a/src/components/gui/export/export-result-button.tsx b/src/components/gui/export/export-result-button.tsx index 7ec4bccf..59c083cf 100644 --- a/src/components/gui/export/export-result-button.tsx +++ b/src/components/gui/export/export-result-button.tsx @@ -14,7 +14,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover"; import OptimizeTableState, { TableSelectionRange, -} from "../table-optimized/OptimizeTableState"; +} from "../table-optimized/optimize-table-state"; export type ExportTarget = "clipboard" | "file"; type ExportFormat = "csv" | "delimited" | "json" | "sql" | "xlsx"; diff --git a/src/components/gui/query-result-table.tsx b/src/components/gui/query-result-table.tsx index 7ecba951..e46a6c16 100644 --- a/src/components/gui/query-result-table.tsx +++ b/src/components/gui/query-result-table.tsx @@ -1,7 +1,7 @@ import OptimizeTable, { OptimizeTableHeaderWithIndexProps, } from "@/components/gui/table-optimized"; -import OptimizeTableState from "@/components/gui/table-optimized/OptimizeTableState"; +import OptimizeTableState from "@/components/gui/table-optimized/optimize-table-state"; import { useStudioContext } from "@/context/driver-provider"; import { ColumnSortOption } from "@/drivers/base-driver"; import { exportDataAsDelimitedText } from "@/lib/export-helper"; diff --git a/src/components/gui/table-cell/create-editable-cell.tsx b/src/components/gui/table-cell/create-editable-cell.tsx index 77516121..c404002e 100644 --- a/src/components/gui/table-cell/create-editable-cell.tsx +++ b/src/components/gui/table-cell/create-editable-cell.tsx @@ -1,11 +1,11 @@ -import { useState, useEffect, useCallback, useRef } from "react"; -import GenericCell from "./generic-cell"; import { DatabaseValue } from "@/drivers/base-driver"; -import OptimizeTableState from "../table-optimized/OptimizeTableState"; -import { useFullEditor } from "../providers/full-editor-provider"; -import { OptimizeTableHeaderWithIndexProps } from "../table-optimized"; import { cn } from "@/lib/utils"; import { ColumnType } from "@outerbase/sdk-transform"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useFullEditor } from "../providers/full-editor-provider"; +import { OptimizeTableHeaderWithIndexProps } from "../table-optimized"; +import OptimizeTableState from "../table-optimized/optimize-table-state"; +import GenericCell from "./generic-cell"; export interface TableEditableCell { value: DatabaseValue; @@ -92,8 +92,8 @@ function InputCellEditor({ type="text" className={ align === "right" - ? "h-full w-full border-0 bg-inherit pl-2 pr-2 text-right font-mono outline-hidden" - : "h-full w-full border-0 bg-inherit pl-2 pr-2 font-mono outline-hidden" + ? "h-full w-full border-0 bg-inherit pr-2 pl-2 text-right font-mono outline-hidden" + : "h-full w-full border-0 bg-inherit pr-2 pl-2 font-mono outline-hidden" } value={value ?? ""} /> diff --git a/src/components/gui/table-optimized/index.tsx b/src/components/gui/table-optimized/index.tsx index fb012a57..6b61ce9a 100644 --- a/src/components/gui/table-optimized/index.tsx +++ b/src/components/gui/table-optimized/index.tsx @@ -12,12 +12,12 @@ import React, { useRef, useState, } from "react"; -import OptimizeTableState from "./OptimizeTableState"; +import OptimizeTableState from "./optimize-table-state"; import OptimizeTableCell from "./table-cell"; import TableFakeBodyPadding from "./table-fake-body-padding"; import TableFakeRowPadding from "./table-fake-row-padding"; import TableHeaderList from "./table-header-list"; -import useTableVisibilityRecalculation from "./useTableVisibilityRecalculation"; +import useTableVisibilityRecalculation from "./use-visibility-calculation"; export type TableCellDecorator = (value: unknown) => ReactElement | null; @@ -138,7 +138,7 @@ function renderCellList({ headers.map((header) => headerSizes[header.index] + "px").join(" "); const onHeaderSizeWithRemap = (idx: number, newWidth: number) => { - onHeaderResize(headerSizes[headers[idx]?.index ?? 0] ?? 150, newWidth); + onHeaderResize(headers[idx]?.index ?? 0, newWidth); }; const windowArray = new Array(rowEnd - rowStart) diff --git a/src/components/gui/table-optimized/OptimizeTableState.tsx b/src/components/gui/table-optimized/optimize-table-state.tsx similarity index 99% rename from src/components/gui/table-optimized/OptimizeTableState.tsx rename to src/components/gui/table-optimized/optimize-table-state.tsx index 5f6529ce..8c794dad 100644 --- a/src/components/gui/table-optimized/OptimizeTableState.tsx +++ b/src/components/gui/table-optimized/optimize-table-state.tsx @@ -495,7 +495,7 @@ export default class OptimizeTableState { } setHeaderWidth(idx: number, newWidth: number) { - return (this.headerWidth[idx] = newWidth); + this.headerWidth[idx] = newWidth; } getHeaderWidth() { diff --git a/src/components/gui/table-optimized/table-cell.tsx b/src/components/gui/table-optimized/table-cell.tsx index 06e946cc..467a0568 100644 --- a/src/components/gui/table-optimized/table-cell.tsx +++ b/src/components/gui/table-optimized/table-cell.tsx @@ -2,7 +2,7 @@ import { cn } from "@/lib/utils"; import { useMemo } from "react"; import { OptimizeTableHeaderWithIndexProps } from "."; import tableResultCellRenderer from "../table-result/render-cell"; -import OptimizeTableState from "./OptimizeTableState"; +import OptimizeTableState from "./optimize-table-state"; export default function OptimizeTableCell({ state, @@ -53,7 +53,8 @@ export default function OptimizeTableCell({ isSelected && "border-neutral-950 dark:border-neutral-50", isBorderBottom && "border-b border-b-neutral-950 dark:border-b-neutral-50", isBorderRight && "border-r border-r-neutral-950 dark:border-r-neutral-50", - isFocus && "shadow-[0_0_0_1px_rgba(0,0,0,0.5)_inset] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.5)_inset]", + isFocus && + "shadow-[0_0_0_1px_rgba(0,0,0,0.5)_inset] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.5)_inset]", isSticky && "sticky", cellBackgroundColor ); diff --git a/src/components/gui/table-optimized/table-header-list.tsx b/src/components/gui/table-optimized/table-header-list.tsx index 5459bd04..1ec81d1f 100644 --- a/src/components/gui/table-optimized/table-header-list.tsx +++ b/src/components/gui/table-optimized/table-header-list.tsx @@ -1,6 +1,6 @@ import { ReactElement } from "react"; import { OptimizeTableHeaderWithIndexProps } from "."; -import OptimizeTableState from "./OptimizeTableState"; +import OptimizeTableState from "./optimize-table-state"; import TableHeader from "./table-header"; export default function TableHeaderList({ @@ -28,9 +28,7 @@ export default function TableHeaderList({ - - - + {headers.map((header, idx) => { return ( diff --git a/src/components/gui/table-optimized/table-header-resize-handler.tsx b/src/components/gui/table-optimized/table-header-resize-handler.tsx index 918c9fb1..623da513 100644 --- a/src/components/gui/table-optimized/table-header-resize-handler.tsx +++ b/src/components/gui/table-optimized/table-header-resize-handler.tsx @@ -1,4 +1,4 @@ -import { useRef, useState, useEffect } from "react"; +import { useEffect, useRef, useState } from "react"; export default function TableHeaderResizeHandler({ idx, @@ -58,7 +58,7 @@ export default function TableHeaderResizeHandler({ tableWrapper.scrollLeft += gain; } - onResize(idx, width); + onResize(idx - 1, width); if (table) { const columns = table.style.gridTemplateColumns.split(" "); diff --git a/src/components/gui/table-optimized/table-header.tsx b/src/components/gui/table-optimized/table-header.tsx index c35ebd9e..80cbbc8f 100644 --- a/src/components/gui/table-optimized/table-header.tsx +++ b/src/components/gui/table-optimized/table-header.tsx @@ -1,8 +1,8 @@ +import { cn } from "@/lib/utils"; import React, { type ReactElement } from "react"; import type { OptimizeTableHeaderWithIndexProps } from "."; +import OptimizeTableState from "./optimize-table-state"; import TableHeaderResizeHandler from "./table-header-resize-handler"; -import OptimizeTableState from "./OptimizeTableState"; -import { cn } from "@/lib/utils"; export default function TableHeader({ idx, diff --git a/src/components/gui/table-optimized/table-state-actions.ts b/src/components/gui/table-optimized/table-state-actions.ts index 9ec4e57e..a6119a74 100644 --- a/src/components/gui/table-optimized/table-state-actions.ts +++ b/src/components/gui/table-optimized/table-state-actions.ts @@ -1,4 +1,4 @@ -import OptimizeTableState from "./OptimizeTableState"; +import OptimizeTableState from "./optimize-table-state"; export default class TableStateActions { static duplicateRow(state: OptimizeTableState) { diff --git a/src/components/gui/table-optimized/useTableVisibilityRecalculation.ts b/src/components/gui/table-optimized/use-visibility-calculation.ts similarity index 97% rename from src/components/gui/table-optimized/useTableVisibilityRecalculation.ts rename to src/components/gui/table-optimized/use-visibility-calculation.ts index d646b3b4..c3fa5872 100644 --- a/src/components/gui/table-optimized/useTableVisibilityRecalculation.ts +++ b/src/components/gui/table-optimized/use-visibility-calculation.ts @@ -1,8 +1,8 @@ +import useElementResize from "@/components/hooks/useElementResize"; import { useCallback, useEffect, useState } from "react"; -import { getVisibleCellRange } from "./helper"; import { OptimizeTableHeaderWithIndexProps } from "."; -import useElementResize from "@/components/hooks/useElementResize"; -import OptimizeTableState from "./OptimizeTableState"; +import { getVisibleCellRange } from "./helper"; +import OptimizeTableState from "./optimize-table-state"; export default function useTableVisibilityRecalculation({ containerRef, diff --git a/src/components/gui/table-result/context-menu.tsx b/src/components/gui/table-result/context-menu.tsx index 27b809de..6e075dc5 100644 --- a/src/components/gui/table-result/context-menu.tsx +++ b/src/components/gui/table-result/context-menu.tsx @@ -10,7 +10,7 @@ import { KEY_BINDING } from "@/lib/key-matcher"; import { LucidePlus, LucideTrash2 } from "lucide-react"; import { useCallback } from "react"; import { useFullEditor } from "../providers/full-editor-provider"; -import OptimizeTableState from "../table-optimized/OptimizeTableState"; +import OptimizeTableState from "../table-optimized/optimize-table-state"; import TableStateActions from "../table-optimized/table-state-actions"; export default function useTableResultContextMenu({ diff --git a/src/components/gui/table-result/filter-column.tsx b/src/components/gui/table-result/filter-column.tsx index 37e57607..48d87c22 100644 --- a/src/components/gui/table-result/filter-column.tsx +++ b/src/components/gui/table-result/filter-column.tsx @@ -14,11 +14,10 @@ import { import { cn } from "@/lib/utils"; import { Check, ListChecks, LucideSettings2 } from "lucide-react"; import { useEffect, useState } from "react"; -import OptimizeTableState from "../table-optimized/OptimizeTableState"; +import OptimizeTableState from "../table-optimized/optimize-table-state"; import { Button } from "@/components/orbit/button"; - export default function useTableResultColumnFilter({ state, }: { @@ -39,11 +38,15 @@ export default function useTableResultColumnFilter({ const filterColumnButton = ( - + Columns {!!columnFilterBadge && ( - + {columnFilterBadge} )} @@ -58,7 +61,7 @@ export default function useTableResultColumnFilter({ onClick={() => { setColumnIndexList(headers.map((_, idx) => idx)); }} - className="relative mx-1 my-1 flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm font-semibold outline-hidden hover:bg-secondary aria-selected:bg-accent aria-selected:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50" + className="hover:bg-secondary aria-selected:bg-accent aria-selected:text-accent-foreground relative mx-1 my-1 flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm font-semibold outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50" > Select all columns diff --git a/src/components/gui/tabs-result/query-result-tab.tsx b/src/components/gui/tabs-result/query-result-tab.tsx index 0d629859..6f6df7c6 100644 --- a/src/components/gui/tabs-result/query-result-tab.tsx +++ b/src/components/gui/tabs-result/query-result-tab.tsx @@ -6,7 +6,7 @@ import AggregateResultButton from "../aggregate-result/aggregate-result-button"; import ExportResultButton from "../export/export-result-button"; import ResultTable from "../query-result-table"; import ResultStats from "../result-stat"; -import OptimizeTableState from "../table-optimized/OptimizeTableState"; +import OptimizeTableState from "../table-optimized/optimize-table-state"; export default function QueryResult({ result, diff --git a/src/components/gui/tabs/table-data-tab.tsx b/src/components/gui/tabs/table-data-tab.tsx index 43741d0d..b3aa1235 100644 --- a/src/components/gui/tabs/table-data-tab.tsx +++ b/src/components/gui/tabs/table-data-tab.tsx @@ -34,7 +34,7 @@ import AggregateResultButton from "../aggregate-result/aggregate-result-button"; import ExportResultButton from "../export/export-result-button"; import OpacityLoading from "../loading-opacity"; import ResultStats from "../result-stat"; -import OptimizeTableState from "../table-optimized/OptimizeTableState"; +import OptimizeTableState from "../table-optimized/optimize-table-state"; import useTableResultColumnFilter from "../table-result/filter-column"; import { Toolbar } from "../toolbar"; import { useCurrentTab } from "../windows-tab"; diff --git a/src/core/extension-manager.tsx b/src/core/extension-manager.tsx index ad787164..e3fa7280 100644 --- a/src/core/extension-manager.tsx +++ b/src/core/extension-manager.tsx @@ -1,5 +1,5 @@ import { OptimizeTableHeaderProps } from "@/components/gui/table-optimized"; -import OptimizeTableState from "@/components/gui/table-optimized/OptimizeTableState"; +import OptimizeTableState from "@/components/gui/table-optimized/optimize-table-state"; import { DatabaseSchemaItem, DatabaseSchemas } from "@/drivers/base-driver"; import { ReactElement } from "react"; import { IStudioExtension } from "./extension-base"; diff --git a/src/drivers/sqlite/sql-parse-table.test.ts b/src/drivers/sqlite/sql-parse-table.test.ts index 08652675..11e680af 100644 --- a/src/drivers/sqlite/sql-parse-table.test.ts +++ b/src/drivers/sqlite/sql-parse-table.test.ts @@ -280,3 +280,71 @@ it("parse strict and without row id table", () => { withoutRowId: true, } as DatabaseTableSchema); }); + +// Regression test for https://github.com/outerbase/studio/issues/403 +it("parse table with foreign key and default", () => { + const sql = `CREATE TABLE "suggestions"( + "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + "entry" INTEGER NOT NULL REFERENCES "entries"("id"), + "user" INTEGER NOT NULL REFERENCES "users"("id"), + "scoreBy" INTEGER DEFAULT NULL REFERENCES "users"("id"), + "updatedAt" INTEGER NOT NULL DEFAULT (UNIXEPOCH()) +)`; + + expect(p(sql)).toEqual({ + tableName: "suggestions", + schemaName: "main", + autoIncrement: true, + pk: ["id"], + columns: [ + { + name: "id", + type: "INTEGER", + pk: true, + constraint: { notNull: true, primaryKey: true, autoIncrement: true }, + }, + { + name: "entry", + type: "INTEGER", + constraint: { + notNull: true, + foreignKey: { + foreignSchemaName: "main", + foreignTableName: "entries", + foreignColumns: ["id"], + }, + }, + }, + { + name: "user", + type: "INTEGER", + constraint: { + notNull: true, + foreignKey: { + foreignSchemaName: "main", + foreignTableName: "users", + foreignColumns: ["id"], + }, + }, + }, + { + name: "scoreBy", + type: "INTEGER", + constraint: { + foreignKey: { + foreignSchemaName: "main", + foreignTableName: "users", + foreignColumns: ["id"], + }, + defaultExpression: "NULL", + }, + }, + { + name: "updatedAt", + type: "INTEGER", + constraint: { notNull: true, defaultExpression: "(UNIXEPOCH())" }, + }, + ], + constraints: [], + } as DatabaseTableSchema); +}); diff --git a/src/drivers/sqlite/sql-parse-table.ts b/src/drivers/sqlite/sql-parse-table.ts index 31d08e88..a312e553 100644 --- a/src/drivers/sqlite/sql-parse-table.ts +++ b/src/drivers/sqlite/sql-parse-table.ts @@ -1,14 +1,14 @@ -import type { SyntaxNode, TreeCursor } from "@lezer/common"; import type { - DatabaseTableColumn, DatabaseColumnConflict, + DatabaseTableColumn, DatabaseTableColumnConstraint, + DatabaseTableFts5, DatabaseTableSchema, SqlOrder, - DatabaseTableFts5, } from "@/drivers/base-driver"; -import { unescapeIdentity } from "./sql-helper"; import { sqliteDialect } from "@/drivers/sqlite/sqlite-dialect"; +import type { SyntaxNode, TreeCursor } from "@lezer/common"; +import { unescapeIdentity } from "./sql-helper"; export class Cursor { protected ptr: SyntaxNode | null; @@ -286,6 +286,7 @@ export function parseColumnConstraint( cursor.next(); } else if (cursor.type() === "Parens") { defaultExpression = cursor.read(); + cursor.next(); } else if ( cursor.match("current_timestamp") || cursor.match("current_time") || @@ -295,6 +296,7 @@ export function parseColumnConstraint( cursor.match("null") ) { defaultExpression = cursor.read(); + cursor.next(); } return { diff --git a/src/extensions/data-decorator/editor.tsx b/src/extensions/data-decorator/editor.tsx index fad9697b..8c04a1fc 100644 --- a/src/extensions/data-decorator/editor.tsx +++ b/src/extensions/data-decorator/editor.tsx @@ -1,5 +1,5 @@ import { OptimizeTableHeaderProps } from "@/components/gui/table-optimized"; -import OptimizeTableState from "@/components/gui/table-optimized/OptimizeTableState"; +import OptimizeTableState from "@/components/gui/table-optimized/optimize-table-state"; import { DropdownMenuItem, DropdownMenuSeparator, diff --git a/src/lib/export-helper.ts b/src/lib/export-helper.ts index d9c6a300..a25fd5c4 100644 --- a/src/lib/export-helper.ts +++ b/src/lib/export-helper.ts @@ -1,5 +1,9 @@ -import { ExportOptions, ExportSelection, ExportTarget } from "@/components/gui/export/export-result-button"; -import OptimizeTableState from "@/components/gui/table-optimized/OptimizeTableState"; +import { + ExportOptions, + ExportSelection, + ExportTarget, +} from "@/components/gui/export/export-result-button"; +import OptimizeTableState from "@/components/gui/table-optimized/optimize-table-state"; import { getSingleTableName } from "@/components/gui/tabs/query-tab"; import { escapeDelimitedValue, diff --git a/src/lib/sql/sql-execute-helper.ts b/src/lib/sql/sql-execute-helper.ts index 60175905..76285643 100644 --- a/src/lib/sql/sql-execute-helper.ts +++ b/src/lib/sql/sql-execute-helper.ts @@ -1,6 +1,6 @@ import OptimizeTableState, { OptimizeTableRowValue, -} from "@/components/gui/table-optimized/OptimizeTableState"; +} from "@/components/gui/table-optimized/optimize-table-state"; import { BaseDriver, DatabaseTableOperation,