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',
|
description: 'Tenant ID for multi-tenancy',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CREATE_TABLE_SCRIPT_STORAGE_KEY = 'sqlQueryManager.lastCreateTableScript'
|
||||||
|
|
||||||
// ─── T-SQL Generator ──────────────────────────────────────────────────────────
|
// ─── T-SQL Generator ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function colToSqlLine(col: ColumnDefinition, addComma = true): string {
|
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) */
|
/** Generate ALTER TABLE diff SQL (edit mode) */
|
||||||
function generateAlterTableSql(
|
function generateAlterTableSql(
|
||||||
originalCols: ColumnDefinition[],
|
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 removeColumn = (id: string) => setColumns((prev) => prev.filter((c) => c.id !== id))
|
||||||
|
|
||||||
const moveColumn = (id: string, direction: 'up' | 'down') => {
|
const moveColumn = (id: string, direction: 'up' | 'down') => {
|
||||||
|
|
@ -1257,8 +1467,7 @@ const SqlTableDesignerDialog = ({
|
||||||
if (isEditMode) return true // table name is locked in edit mode
|
if (isEditMode) return true // table name is locked in edit mode
|
||||||
const baseOk =
|
const baseOk =
|
||||||
!!settings.tableName.trim() && !!settings.entityName.trim() && !!settings.menuValue
|
!!settings.tableName.trim() && !!settings.entityName.trim() && !!settings.menuValue
|
||||||
if (!baseOk) return false
|
return baseOk
|
||||||
return columns.some((c) => c.columnName.trim().toLowerCase() === 'id')
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -1395,6 +1604,15 @@ const SqlTableDesignerDialog = ({
|
||||||
<Button size="xs" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
|
<Button size="xs" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
|
||||||
{translate('::App.SqlQueryManager.AddMultiTenantColumns')}
|
{translate('::App.SqlQueryManager.AddMultiTenantColumns')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="solid"
|
||||||
|
color="amber-600"
|
||||||
|
onClick={importColumnsFromRememberedCreateTable}
|
||||||
|
disabled={isEditMode}
|
||||||
|
>
|
||||||
|
CREATE TABLE'dan Kolonları Yapıştır
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -1565,10 +1783,11 @@ const SqlTableDesignerDialog = ({
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Id warning */}
|
{/* PK guidance */}
|
||||||
{!isEditMode && !columns.some((c) => c.columnName.trim().toLowerCase() === 'id') && (
|
{!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">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
@ -1668,8 +1887,9 @@ const SqlTableDesignerDialog = ({
|
||||||
|
|
||||||
{/* Warning: no Id column */}
|
{/* Warning: no Id column */}
|
||||||
{!isEditMode && !columns.some((c) => c.columnName.trim().toLowerCase() === 'id') && (
|
{!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">
|
<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">
|
||||||
{translate('::App.SqlQueryManager.NoIdColumnError')}
|
Full Audited kolonları seçmediğiniz için PK kolonu manuel tanımlanmalıdır. Bu kolon
|
||||||
|
int veya uniqueidentifier olabilir.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue