mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-07 07:03:34 +08:00
🐛 fix(data-grid): 修复 MySQL bit 字段复制 INSERT 引号错误
- 识别 MySQL 家族 bit 列并生成未加引号的数值字面量 - 兼容布尔、十进制、十六进制和二进制值格式 - 增加复制 INSERT 回归测试并保留普通字段序列化行为 Fixes #786
This commit is contained in:
@@ -78,6 +78,50 @@ describe('buildCopyInsertSQL', () => {
|
||||
expect(sql).toBe("INSERT INTO `users` (`name`, `id`) VALUES ('Ada', '7');");
|
||||
});
|
||||
|
||||
it('emits unquoted MySQL bit literals while keeping ordinary values quoted', () => {
|
||||
const sql = buildCopyInsertSQL({
|
||||
dbType: 'mysql',
|
||||
tableName: 'flags',
|
||||
orderedCols: ['disabled', 'enabled', 'mask', 'note'],
|
||||
record: {
|
||||
disabled: 0,
|
||||
enabled: 1,
|
||||
mask: '0x05',
|
||||
note: '1',
|
||||
},
|
||||
columnTypesByLowerName: {
|
||||
disabled: 'bit(1)',
|
||||
enabled: 'bit(1)',
|
||||
mask: 'bit(4)',
|
||||
note: 'varchar(16)',
|
||||
},
|
||||
});
|
||||
|
||||
expect(sql).toBe("INSERT INTO `flags` (`disabled`, `enabled`, `mask`, `note`) VALUES (0, 1, 5, '1');");
|
||||
});
|
||||
|
||||
it('normalizes MySQL bit binary and decimal text values', () => {
|
||||
const sql = buildCopyInsertSQL({
|
||||
dbType: 'mysql',
|
||||
tableName: 'flags',
|
||||
orderedCols: ['zero', 'binary_value', 'large_value'],
|
||||
record: {
|
||||
zero: '0',
|
||||
binary_value: "b'101'",
|
||||
large_value: '18446744073709551615',
|
||||
},
|
||||
columnTypesByLowerName: {
|
||||
zero: 'BIT',
|
||||
binary_value: 'bit(8)',
|
||||
large_value: 'bit(64)',
|
||||
},
|
||||
});
|
||||
|
||||
expect(sql).toBe(
|
||||
"INSERT INTO `flags` (`zero`, `binary_value`, `large_value`) VALUES (0, 5, 18446744073709551615);",
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps RFC3339-looking text unchanged for non-temporal columns', () => {
|
||||
const sql = buildCopyInsertSQL({
|
||||
dbType: 'postgres',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IndexDefinition } from '../types';
|
||||
import { escapeLiteral, quoteIdentPart, quoteQualifiedIdent } from '../utils/sql';
|
||||
import { isOracleLikeDialect } from '../utils/sqlDialect';
|
||||
import { isMysqlFamilyDialect, isOracleLikeDialect } from '../utils/sqlDialect';
|
||||
|
||||
type BuildCopyInsertSQLParams = {
|
||||
dbType: string;
|
||||
@@ -212,10 +212,71 @@ const formatOracleTemporalLiteral = (value: any, columnType?: string): string |
|
||||
return `TO_DATE('${escaped}', 'YYYY-MM-DD HH24:MI:SS')`;
|
||||
};
|
||||
|
||||
const isMySQLBitColumnType = (columnType?: string): boolean => {
|
||||
const normalized = String(columnType || '').trim().toLowerCase();
|
||||
return /^bit(?:\s*\(\s*\d+\s*\))?$/.test(normalized);
|
||||
};
|
||||
|
||||
const formatMySQLBitLiteral = (value: any): string | null => {
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? '1' : '0';
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
if (!Number.isSafeInteger(value) || value < 0) return null;
|
||||
return BigInt(value).toString(10);
|
||||
}
|
||||
|
||||
if (typeof value === 'bigint') {
|
||||
if (value < 0n) return null;
|
||||
return value.toString(10);
|
||||
}
|
||||
|
||||
if (typeof value !== 'string') return null;
|
||||
const raw = value.trim();
|
||||
if (!raw) return null;
|
||||
|
||||
const binaryLiteral = raw.match(/^[bB]['"]([01]+)['"]$/);
|
||||
if (binaryLiteral) {
|
||||
try {
|
||||
return BigInt(`0b${binaryLiteral[1]}`).toString(10);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let numericText = raw;
|
||||
let radix = 10;
|
||||
if (/^0[bB][01]+$/.test(raw)) {
|
||||
numericText = raw.slice(2);
|
||||
radix = 2;
|
||||
} else if (/^0[xX][0-9a-f]+$/.test(raw)) {
|
||||
numericText = raw.slice(2);
|
||||
radix = 16;
|
||||
} else if (!/^\d+$/.test(raw)) {
|
||||
if (/^(true|false)$/i.test(raw)) {
|
||||
return raw.toLowerCase() === 'true' ? '1' : '0';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return BigInt(radix === 10 ? raw : `${radix === 2 ? '0b' : '0x'}${numericText}`).toString(10);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const formatCopySqlLiteral = (value: any, columnType?: string, dbType = ''): string => {
|
||||
if (value === null || value === undefined) {
|
||||
return 'NULL';
|
||||
}
|
||||
if (isMysqlFamilyDialect(dbType) && isMySQLBitColumnType(columnType)) {
|
||||
const mysqlBitLiteral = formatMySQLBitLiteral(value);
|
||||
if (mysqlBitLiteral) {
|
||||
return mysqlBitLiteral;
|
||||
}
|
||||
}
|
||||
if (isOracleLikeDialect(dbType)) {
|
||||
const oracleTemporalLiteral = formatOracleTemporalLiteral(value, columnType);
|
||||
if (oracleTemporalLiteral) {
|
||||
|
||||
Reference in New Issue
Block a user