mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
feat: add frontend index ops and feature flag for db manager
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ export type DbFeatures = {
|
||||
primaryKeys?: boolean
|
||||
defaultValues?: boolean
|
||||
schemas?: boolean
|
||||
indexes?: boolean
|
||||
}
|
||||
|
||||
export function getDbFeatures(dbInput: DbInput): Required<DbFeatures> {
|
||||
@@ -17,7 +18,10 @@ export function getDbFeatures(dbInput: DbInput): Required<DbFeatures> {
|
||||
primaryKeys: true,
|
||||
defaultValues: true,
|
||||
enforcedForeignKeys: true,
|
||||
schemas: dbInput.type !== 'ducklake' && dbSupportsSchemas(dbInput.resourceType)
|
||||
schemas: dbInput.type !== 'ducklake' && dbSupportsSchemas(dbInput.resourceType),
|
||||
// Index management is currently PostgreSQL-only (backend markers reject
|
||||
// other dialects). Structured as a flag so it can be widened later.
|
||||
indexes: dbInput.type !== 'ducklake' && dbInput.resourceType === 'postgresql'
|
||||
}
|
||||
|
||||
if (dbInput.type == 'ducklake')
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// Index management types and transforms.
|
||||
//
|
||||
// Indexes are managed imperatively (create / drop happen immediately), so unlike
|
||||
// foreign keys we do not round-trip a structured column model back from the DB —
|
||||
// the listing shows the canonical definition string returned by the database.
|
||||
// Currently PostgreSQL-only; the backend markers reject other dialects.
|
||||
|
||||
/** A single key of a to-be-created index. */
|
||||
export type IndexColumnInput = {
|
||||
value: string
|
||||
/** When true, `value` is an arbitrary SQL expression (e.g. `lower(email)`)
|
||||
* rather than a plain column name. */
|
||||
isExpression?: boolean
|
||||
}
|
||||
|
||||
/** User-provided values for creating an index. */
|
||||
export type CreateIndexInput = {
|
||||
name?: string
|
||||
columns: IndexColumnInput[]
|
||||
unique?: boolean
|
||||
/** Index access method: btree (default) / hash / gin / gist / brin / spgist. */
|
||||
method?: string
|
||||
/** Partial-index predicate (raw SQL, placed after WHERE). */
|
||||
where?: string
|
||||
/** Covering (INCLUDE) columns. */
|
||||
include?: string[]
|
||||
/** Build the index without locking the table against writes. */
|
||||
concurrent?: boolean
|
||||
}
|
||||
|
||||
/** Raw row shape returned by the LIST_INDEXES marker (PostgreSQL). */
|
||||
export type RawIndex = {
|
||||
index_name: string
|
||||
index_def: string
|
||||
is_unique: boolean | string | number
|
||||
is_primary: boolean | string | number
|
||||
is_valid: boolean | string | number
|
||||
method: string
|
||||
size_bytes: number | string | null
|
||||
backs_constraint: boolean | string | number
|
||||
}
|
||||
|
||||
/** Display model for an existing index. */
|
||||
export type DbIndex = {
|
||||
name: string
|
||||
definition: string
|
||||
isUnique: boolean
|
||||
isPrimary: boolean
|
||||
isValid: boolean
|
||||
method: string
|
||||
sizeBytes: number
|
||||
/** Backs a PK/unique constraint — cannot be dropped directly (read-only). */
|
||||
backsConstraint: boolean
|
||||
}
|
||||
|
||||
// Postgres booleans can arrive as true/false, 't'/'f', or 1/0 depending on how
|
||||
// the driver serializes the result — coerce all of them.
|
||||
function coerceBool(v: boolean | string | number | null | undefined): boolean {
|
||||
if (typeof v === 'boolean') return v
|
||||
if (typeof v === 'number') return v !== 0
|
||||
if (typeof v === 'string') return v === 't' || v.toLowerCase() === 'true' || v === '1'
|
||||
return false
|
||||
}
|
||||
|
||||
function coerceNumber(v: number | string | null | undefined): number {
|
||||
if (typeof v === 'number') return v
|
||||
if (typeof v === 'string') {
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
export function transformIndexes(raw: RawIndex[]): DbIndex[] {
|
||||
if (!raw || !Array.isArray(raw)) return []
|
||||
return raw.map((r) => ({
|
||||
name: r.index_name,
|
||||
definition: r.index_def,
|
||||
isUnique: coerceBool(r.is_unique),
|
||||
isPrimary: coerceBool(r.is_primary),
|
||||
isValid: coerceBool(r.is_valid),
|
||||
method: r.method,
|
||||
sizeBytes: coerceNumber(r.size_bytes),
|
||||
backsConstraint: coerceBool(r.backs_constraint)
|
||||
}))
|
||||
}
|
||||
|
||||
/** Human-readable byte size for display (e.g. "12 kB"). */
|
||||
export function formatIndexSize(bytes: number): string {
|
||||
if (!bytes || bytes <= 0) return '0 B'
|
||||
const units = ['B', 'kB', 'MB', 'GB', 'TB']
|
||||
let i = 0
|
||||
let n = bytes
|
||||
while (n >= 1024 && i < units.length - 1) {
|
||||
n /= 1024
|
||||
i++
|
||||
}
|
||||
return `${i === 0 ? n : n.toFixed(1)} ${units[i]}`
|
||||
}
|
||||
@@ -18,6 +18,12 @@ import {
|
||||
transformSnowflakeForeignKeys,
|
||||
type RawForeignKey
|
||||
} from './apps/components/display/dbtable/queries/relationalKeys'
|
||||
import {
|
||||
transformIndexes,
|
||||
type CreateIndexInput,
|
||||
type DbIndex,
|
||||
type RawIndex
|
||||
} from './apps/components/display/dbtable/queries/indexes'
|
||||
|
||||
export type IDbTableOps = {
|
||||
dbType: DbType
|
||||
@@ -282,6 +288,88 @@ export function dbSchemaOpsWithPreviewScripts({
|
||||
}
|
||||
}
|
||||
|
||||
export type IDbIndexOps = {
|
||||
listIndexes: (params: { tableKey: string; schema?: string }) => Promise<DbIndex[]>
|
||||
createIndex: (params: {
|
||||
tableKey: string
|
||||
schema?: string
|
||||
values: CreateIndexInput
|
||||
}) => Promise<void>
|
||||
previewCreateIndexSql: (params: {
|
||||
tableKey: string
|
||||
schema?: string
|
||||
values: CreateIndexInput
|
||||
}) => Promise<string>
|
||||
dropIndex: (params: { name: string; schema?: string; concurrent?: boolean }) => Promise<void>
|
||||
}
|
||||
|
||||
export function dbIndexOpsWithPreviewScripts({
|
||||
workspace,
|
||||
input
|
||||
}: {
|
||||
workspace: string
|
||||
input: DbInput
|
||||
}): IDbIndexOps {
|
||||
const dbType = getDbType(input)
|
||||
const dbArg = getDatabaseArg(input)
|
||||
const language = getLanguageByResourceType(dbType)
|
||||
|
||||
function makeMarker(op: string, payload: Record<string, unknown>): string {
|
||||
return `-- WM_INTERNAL_DB_${op} ${JSON.stringify(payload)}`
|
||||
}
|
||||
|
||||
function createIndexPayload({
|
||||
tableKey,
|
||||
schema,
|
||||
values
|
||||
}: {
|
||||
tableKey: string
|
||||
schema?: string
|
||||
values: CreateIndexInput
|
||||
}): Record<string, unknown> {
|
||||
return {
|
||||
name: values.name?.trim() || undefined,
|
||||
table: tableKey,
|
||||
schema,
|
||||
columns: values.columns,
|
||||
unique: values.unique ?? false,
|
||||
method: values.method,
|
||||
where: values.where?.trim() || undefined,
|
||||
include: values.include ?? [],
|
||||
concurrent: values.concurrent ?? false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
listIndexes: async ({ tableKey, schema }) => {
|
||||
const content = makeMarker('LIST_INDEXES', { table: tableKey, schema })
|
||||
const result = (await runScriptAndPollResult({
|
||||
workspace,
|
||||
requestBody: { args: { ...dbArg }, language, content }
|
||||
})) as RawIndex[]
|
||||
return transformIndexes(result)
|
||||
},
|
||||
createIndex: async ({ tableKey, schema, values }) => {
|
||||
const content = makeMarker('CREATE_INDEX', createIndexPayload({ tableKey, schema, values }))
|
||||
await runScriptAndPollResult({
|
||||
workspace,
|
||||
requestBody: { args: { ...dbArg }, language, content }
|
||||
})
|
||||
},
|
||||
previewCreateIndexSql: async ({ tableKey, schema, values }) => {
|
||||
const content = makeMarker('CREATE_INDEX', createIndexPayload({ tableKey, schema, values }))
|
||||
return expandMarker(workspace, language, content)
|
||||
},
|
||||
dropIndex: async ({ name, schema, concurrent }) => {
|
||||
const content = makeMarker('DROP_INDEX', { name, schema, concurrent: concurrent ?? false })
|
||||
await runScriptAndPollResult({
|
||||
workspace,
|
||||
requestBody: { args: { ...dbArg }, language, content }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDucklakeSchema({
|
||||
workspace,
|
||||
ducklake
|
||||
|
||||
Reference in New Issue
Block a user