Implement base64 encoding for binary data in SQL dumper and modify row handling in MySQL and MSSQL drivers

This commit is contained in:
Stela Augustinova
2025-11-06 12:30:48 +01:00
parent 37d54811e0
commit dca9ea24d7
4 changed files with 52 additions and 21 deletions

View File

@@ -79,7 +79,12 @@ export class SqlDumper implements AlterProcessor {
else if (_isDate(value)) this.putStringValue(new Date(value).toISOString()); else if (_isDate(value)) this.putStringValue(new Date(value).toISOString());
else if (value?.type == 'Buffer' && _isArray(value?.data)) this.putByteArrayValue(value?.data); else if (value?.type == 'Buffer' && _isArray(value?.data)) this.putByteArrayValue(value?.data);
else if (value?.$binary?.base64) { else if (value?.$binary?.base64) {
this.putByteArrayValue(Array.from(Buffer.from(value.$binary.base64, 'base64'))); const binary = atob(value.$binary.base64);
const bytes = new Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
this.putByteArrayValue(bytes);
} }
else if (value?.$bigint) this.putRaw(value?.$bigint); else if (value?.$bigint) this.putRaw(value?.$bigint);
else if (_isPlainObject(value) || _isArray(value)) this.putStringValue(JSON.stringify(value)); else if (_isPlainObject(value) || _isArray(value)) this.putStringValue(JSON.stringify(value));

View File

@@ -51,6 +51,11 @@ export function base64ToHex(base64String) {
return '0x' + hexString.toUpperCase(); return '0x' + hexString.toUpperCase();
}; };
export function hexToBase64(hexString) {
const binaryString = hexString.match(/.{1,2}/g).map(byte => String.fromCharCode(parseInt(byte, 16))).join('');
return btoa(binaryString);
}
export function parseCellValue(value, editorTypes?: DataEditorTypesBehaviour) { export function parseCellValue(value, editorTypes?: DataEditorTypesBehaviour) {
if (!_isString(value)) return value; if (!_isString(value)) return value;
@@ -62,9 +67,10 @@ export function parseCellValue(value, editorTypes?: DataEditorTypesBehaviour) {
const mHex = value.match(/^0x([0-9a-fA-F][0-9a-fA-F])+$/); const mHex = value.match(/^0x([0-9a-fA-F][0-9a-fA-F])+$/);
if (mHex) { if (mHex) {
return { return {
type: 'Buffer', $binary: {
data: hexStringToArray(value.substring(2)), base64: hexToBase64(value.substring(2))
}; }
}
} }
} }
@@ -246,10 +252,11 @@ export function stringifyCellValue(
} }
if (editorTypes?.parseHexAsBuffer) { if (editorTypes?.parseHexAsBuffer) {
if (value?.type == 'Buffer' && _isArray(value.data)) { // if (value?.type == 'Buffer' && _isArray(value.data)) {
return { value: '0x' + arrayToHexString(value.data), gridStyle: 'valueCellStyle' }; // return { value: '0x' + arrayToHexString(value.data), gridStyle: 'valueCellStyle' };
} // }
} }
if (editorTypes?.parseObjectIdAsDollar) { if (editorTypes?.parseObjectIdAsDollar) {
if (value?.$oid) { if (value?.$oid) {
switch (intent) { switch (intent) {

View File

@@ -24,6 +24,15 @@ function extractTediousColumns(columns, addDriverNativeColumn = false) {
return res; return res;
} }
function modifyRow(row, columns) {
columns.forEach((col) => {
if (Buffer.isBuffer(row[col.columnName])) {
row[col.columnName] = { $binary: { base64: Buffer.from(row[col.columnName]).toString('base64') } };
}
});
return row;
}
async function getDefaultAzureSqlToken() { async function getDefaultAzureSqlToken() {
const credential = new ManagedIdentityCredential(); const credential = new ManagedIdentityCredential();
const tokenResponse = await credential.getToken('https://database.windows.net/.default'); const tokenResponse = await credential.getToken('https://database.windows.net/.default');
@@ -121,9 +130,12 @@ async function tediousQueryCore(dbhan, sql, options) {
}); });
request.on('row', function (columns) { request.on('row', function (columns) {
result.rows.push( result.rows.push(
modifyRow(
_.zipObject( _.zipObject(
result.columns.map(x => x.columnName), result.columns.map(x => x.columnName),
columns.map(x => x.value) columns.map(x => x.value)
),
result.columns
) )
); );
}); });
@@ -148,13 +160,17 @@ async function tediousReadQuery(dbhan, sql, structure) {
currentColumns = extractTediousColumns(columns); currentColumns = extractTediousColumns(columns);
pass.write({ pass.write({
__isStreamHeader: true, __isStreamHeader: true,
engine: 'mssql@dbgate-plugin-mssql',
...(structure || { columns: currentColumns }), ...(structure || { columns: currentColumns }),
}); });
}); });
request.on('row', function (columns) { request.on('row', function (columns) {
const row = _.zipObject( const row = modifyRow(
_.zipObject(
currentColumns.map(x => x.columnName), currentColumns.map(x => x.columnName),
columns.map(x => x.value) columns.map(x => x.value)
),
currentColumns
); );
pass.write(row); pass.write(row);
}); });
@@ -204,12 +220,15 @@ async function tediousStream(dbhan, sql, options) {
}); });
request.on('columnMetadata', function (columns) { request.on('columnMetadata', function (columns) {
currentColumns = extractTediousColumns(columns); currentColumns = extractTediousColumns(columns);
options.recordset(currentColumns); options.recordset(currentColumns, { engine: 'mssql@dbgate-plugin-mssql' });
}); });
request.on('row', function (columns) { request.on('row', function (columns) {
const row = _.zipObject( const row = modifyRow(
_.zipObject(
currentColumns.map(x => x.columnName), currentColumns.map(x => x.columnName),
columns.map(x => x.value) columns.map(x => x.value)
),
currentColumns
); );
options.row(row); options.row(row);
}); });

View File

@@ -21,7 +21,7 @@ function extractColumns(fields) {
return null; return null;
} }
function transformRow(row, columns) { function modifyRow(row, columns) {
columns.forEach((col) => { columns.forEach((col) => {
if (Buffer.isBuffer(row[col.columnName])) { if (Buffer.isBuffer(row[col.columnName])) {
row[col.columnName] = { $binary: { base64: Buffer.from(row[col.columnName]).toString('base64') } }; row[col.columnName] = { $binary: { base64: Buffer.from(row[col.columnName]).toString('base64') } };
@@ -106,7 +106,7 @@ const drivers = driverBases.map(driverBase => ({
dbhan.client.query(sql, function (error, results, fields) { dbhan.client.query(sql, function (error, results, fields) {
if (error) reject(error); if (error) reject(error);
const columns = extractColumns(fields); const columns = extractColumns(fields);
resolve({ rows: results && columns && results.map && results.map(row => transformRow(zipDataRow(row, columns), columns)), columns }); resolve({ rows: results && columns && results.map && results.map(row => modifyRow(zipDataRow(row, columns), columns)), columns });
}); });
}); });
}, },
@@ -139,7 +139,7 @@ const drivers = driverBases.map(driverBase => ({
options.recordset(columns, { engine: driverBase.engine }); options.recordset(columns, { engine: driverBase.engine });
} else { } else {
if (columns) { if (columns) {
options.row(transformRow(zipDataRow(row, columns), columns)); options.row(modifyRow(zipDataRow(row, columns), columns));
} }
} }
}; };
@@ -184,7 +184,7 @@ const drivers = driverBases.map(driverBase => ({
...(structure || { columns }), ...(structure || { columns }),
}); });
}) })
.on('result', row => pass.write(transformRow(zipDataRow(row, columns), columns))) .on('result', row => pass.write(modifyRow(zipDataRow(row, columns), columns)))
.on('end', () => pass.end()); .on('end', () => pass.end());
return pass; return pass;