CREATE TABLE sütunları kopyala
This commit is contained in:
parent
283fb2eba2
commit
52e52bb03c
1 changed files with 226 additions and 6 deletions
|
|
@ -224,6 +224,8 @@ const TENANT_COLUMN: ColumnDefinition = {
|
|||
description: 'Tenant ID for multi-tenancy',
|
||||
}
|
||||
|
||||
const CREATE_TABLE_SCRIPT_STORAGE_KEY = 'sqlQueryManager.lastCreateTableScript'
|
||||
|
||||
// ─── T-SQL Generator ──────────────────────────────────────────────────────────
|
||||
|
||||
function colToSqlLine(col: ColumnDefinition, addComma = true): string {
|
||||
|
|
@ -377,6 +379,136 @@ function dbColToColumnDef(col: {
|
|||
}
|
||||
}
|
||||
|
||||
function splitByTopLevelComma(input: string): string[] {
|
||||
const parts: string[] = []
|
||||
let current = ''
|
||||
let depth = 0
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const ch = input[i]
|
||||
if (ch === '(') depth++
|
||||
if (ch === ')') depth = Math.max(0, depth - 1)
|
||||
|
||||
if (ch === ',' && depth === 0) {
|
||||
if (current.trim()) parts.push(current.trim())
|
||||
current = ''
|
||||
continue
|
||||
}
|
||||
|
||||
current += ch
|
||||
}
|
||||
|
||||
if (current.trim()) parts.push(current.trim())
|
||||
return parts
|
||||
}
|
||||
|
||||
function unwrapDefaultExpression(value: string): string {
|
||||
let result = value.trim()
|
||||
while (result.startsWith('(') && result.endsWith(')')) {
|
||||
result = result.slice(1, -1).trim()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function mapSqlTypeToDesigner(dataTypeRaw: string, lengthRaw: string): {
|
||||
dataType: SqlDataType
|
||||
maxLength: string
|
||||
} {
|
||||
const dt = dataTypeRaw.toLowerCase()
|
||||
const len = lengthRaw.toLowerCase()
|
||||
|
||||
if (dt === 'nvarchar' || dt === 'varchar' || dt === 'char' || dt === 'nchar') {
|
||||
if (len === 'max') {
|
||||
return { dataType: 'nvarchar(MAX)', maxLength: '' }
|
||||
}
|
||||
return { dataType: 'nvarchar', maxLength: len || '100' }
|
||||
}
|
||||
|
||||
if (dt === 'int') return { dataType: 'int', maxLength: '' }
|
||||
if (dt === 'bigint') return { dataType: 'bigint', maxLength: '' }
|
||||
if (dt === 'decimal' || dt === 'numeric') return { dataType: 'decimal', maxLength: '' }
|
||||
if (dt === 'float' || dt === 'real') return { dataType: 'float', maxLength: '' }
|
||||
if (dt === 'bit') return { dataType: 'bit', maxLength: '' }
|
||||
if (dt.startsWith('datetime') || dt === 'smalldatetime' || dt === 'time') {
|
||||
return { dataType: 'datetime2', maxLength: '' }
|
||||
}
|
||||
if (dt === 'date') return { dataType: 'date', maxLength: '' }
|
||||
if (dt === 'uniqueidentifier') return { dataType: 'uniqueidentifier', maxLength: '' }
|
||||
if (dt === 'money' || dt === 'smallmoney') return { dataType: 'money', maxLength: '' }
|
||||
|
||||
return { dataType: 'nvarchar', maxLength: '100' }
|
||||
}
|
||||
|
||||
function parseCreateTableColumns(script: string): ColumnDefinition[] {
|
||||
if (!script?.trim()) return []
|
||||
|
||||
const createIdx = script.search(/\bCREATE\s+TABLE\b/i)
|
||||
if (createIdx < 0) return []
|
||||
|
||||
const openParenIdx = script.indexOf('(', createIdx)
|
||||
if (openParenIdx < 0) return []
|
||||
|
||||
let depth = 0
|
||||
let closeParenIdx = -1
|
||||
for (let i = openParenIdx; i < script.length; i++) {
|
||||
const ch = script[i]
|
||||
if (ch === '(') depth++
|
||||
if (ch === ')') {
|
||||
depth--
|
||||
if (depth === 0) {
|
||||
closeParenIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closeParenIdx < 0) return []
|
||||
|
||||
const body = script.slice(openParenIdx + 1, closeParenIdx)
|
||||
const defs = splitByTopLevelComma(body)
|
||||
const columns: ColumnDefinition[] = []
|
||||
|
||||
for (const def of defs) {
|
||||
const trimmed = def.trim()
|
||||
if (!trimmed) continue
|
||||
|
||||
if (
|
||||
/^(CONSTRAINT|PRIMARY\s+KEY|UNIQUE\s+KEY|UNIQUE|FOREIGN\s+KEY|CHECK)\b/i.test(trimmed)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
const colMatch = trimmed.match(/^\[([^\]]+)\]\s+(.+)$/i)
|
||||
if (!colMatch) continue
|
||||
|
||||
const columnName = colMatch[1].trim()
|
||||
const rest = colMatch[2].trim()
|
||||
|
||||
const typeMatch = rest.match(/^\[?([A-Za-z0-9_]+)\]?\s*(?:\(([^)]*)\))?/)
|
||||
if (!typeMatch) continue
|
||||
|
||||
const sqlType = typeMatch[1] ?? 'nvarchar'
|
||||
const sqlLength = (typeMatch[2] ?? '').trim()
|
||||
const mapped = mapSqlTypeToDesigner(sqlType, sqlLength)
|
||||
|
||||
const isNullable = !/\bNOT\s+NULL\b/i.test(rest)
|
||||
const defaultMatch = rest.match(/\bDEFAULT\b\s+(.+)$/i)
|
||||
const defaultValue = defaultMatch ? unwrapDefaultExpression(defaultMatch[1]) : ''
|
||||
|
||||
columns.push({
|
||||
id: crypto.randomUUID(),
|
||||
columnName,
|
||||
dataType: mapped.dataType,
|
||||
maxLength: mapped.maxLength,
|
||||
isNullable,
|
||||
defaultValue,
|
||||
description: '',
|
||||
})
|
||||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
/** Generate ALTER TABLE diff SQL (edit mode) */
|
||||
function generateAlterTableSql(
|
||||
originalCols: ColumnDefinition[],
|
||||
|
|
@ -1025,6 +1157,84 @@ const SqlTableDesignerDialog = ({
|
|||
}
|
||||
}
|
||||
|
||||
const importColumnsFromRememberedCreateTable = async () => {
|
||||
let script = ''
|
||||
|
||||
try {
|
||||
const clipboardText = await navigator.clipboard.readText()
|
||||
if (/\bCREATE\s+TABLE\b/i.test(clipboardText)) {
|
||||
script = clipboardText
|
||||
localStorage.setItem(CREATE_TABLE_SCRIPT_STORAGE_KEY, clipboardText)
|
||||
}
|
||||
} catch {
|
||||
// Clipboard read can fail due to browser permissions; fallback to saved script.
|
||||
}
|
||||
|
||||
if (!script) {
|
||||
script = localStorage.getItem(CREATE_TABLE_SCRIPT_STORAGE_KEY) || ''
|
||||
}
|
||||
|
||||
if (!script.trim()) {
|
||||
toast.push(
|
||||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
CREATE TABLE script bulunamadı. Scripti panoya kopyalayıp tekrar deneyin.
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const parsedColumns = parseCreateTableColumns(script)
|
||||
if (parsedColumns.length === 0) {
|
||||
toast.push(
|
||||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
CREATE TABLE scriptinden sütunlar ayrıştırılamadı.
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let addedCount = 0
|
||||
let skippedCount = 0
|
||||
|
||||
setColumns((prev) => {
|
||||
const existingNonEmpty = prev.filter((c) => c.columnName.trim() !== '')
|
||||
const existingNames = new Set(
|
||||
existingNonEmpty.map((c) => c.columnName.trim().toLowerCase()),
|
||||
)
|
||||
|
||||
const toAdd: ColumnDefinition[] = []
|
||||
for (const col of parsedColumns) {
|
||||
const key = col.columnName.trim().toLowerCase()
|
||||
if (!key) {
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
if (existingNames.has(key)) {
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
existingNames.add(key)
|
||||
toAdd.push(col)
|
||||
}
|
||||
|
||||
addedCount = toAdd.length
|
||||
return [...existingNonEmpty, ...toAdd, createEmptyColumn()]
|
||||
})
|
||||
|
||||
toast.push(
|
||||
<Notification type="success" title={translate('::App.Platform.Success')}>
|
||||
{addedCount > 0
|
||||
? `${addedCount} sütun eklendi${skippedCount > 0 ? `, ${skippedCount} sütun zaten mevcut olduğu için atlandı.` : '.'}`
|
||||
: 'Yeni sütun eklenmedi. Scriptteki sütunların tamamı zaten mevcut.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
}
|
||||
|
||||
const removeColumn = (id: string) => setColumns((prev) => prev.filter((c) => c.id !== id))
|
||||
|
||||
const moveColumn = (id: string, direction: 'up' | 'down') => {
|
||||
|
|
@ -1257,8 +1467,7 @@ const SqlTableDesignerDialog = ({
|
|||
if (isEditMode) return true // table name is locked in edit mode
|
||||
const baseOk =
|
||||
!!settings.tableName.trim() && !!settings.entityName.trim() && !!settings.menuValue
|
||||
if (!baseOk) return false
|
||||
return columns.some((c) => c.columnName.trim().toLowerCase() === 'id')
|
||||
return baseOk
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -1395,6 +1604,15 @@ const SqlTableDesignerDialog = ({
|
|||
<Button size="xs" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
|
||||
{translate('::App.SqlQueryManager.AddMultiTenantColumns')}
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="solid"
|
||||
color="amber-600"
|
||||
onClick={importColumnsFromRememberedCreateTable}
|
||||
disabled={isEditMode}
|
||||
>
|
||||
CREATE TABLE'dan Kolonları Yapıştır
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
|
|
@ -1565,10 +1783,11 @@ const SqlTableDesignerDialog = ({
|
|||
})}
|
||||
</div>
|
||||
|
||||
{/* Id warning */}
|
||||
{/* PK guidance */}
|
||||
{!isEditMode && !columns.some((c) => c.columnName.trim().toLowerCase() === 'id') && (
|
||||
<div className="px-2 py-1.5 mt-2 bg-orange-50 dark:bg-orange-900/20 border border-orange-300 dark:border-orange-700 rounded text-xs text-orange-700 dark:text-orange-300">
|
||||
{translate('::App.SqlQueryManager.NoIdColumnWarning')}
|
||||
PK için Id kolonu (int veya uniqueidentifier) önerilir. İsterseniz bir sonraki adımda
|
||||
Primary Key de tanımlayabilirsiniz.
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -1668,8 +1887,9 @@ const SqlTableDesignerDialog = ({
|
|||
|
||||
{/* Warning: no Id column */}
|
||||
{!isEditMode && !columns.some((c) => c.columnName.trim().toLowerCase() === 'id') && (
|
||||
<div className="px-3 py-2 bg-red-50 dark:bg-red-900/20 border border-red-300 dark:border-red-700 rounded text-xs text-red-700 dark:text-red-300">
|
||||
{translate('::App.SqlQueryManager.NoIdColumnError')}
|
||||
<div className="px-3 py-2 bg-amber-50 dark:bg-amber-900/20 border border-amber-300 dark:border-amber-700 rounded text-xs text-amber-700 dark:text-amber-300">
|
||||
Full Audited kolonları seçmediğiniz için PK kolonu manuel tanımlanmalıdır. Bu kolon
|
||||
int veya uniqueidentifier olabilir.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue