bigint support #1087

This commit is contained in:
SPRINX0\prochazka
2025-05-05 16:04:21 +02:00
parent 23db345756
commit 110d87e512
11 changed files with 93 additions and 15 deletions

View File

@@ -12,6 +12,7 @@ const {
ScriptWriterEval, ScriptWriterEval,
SqlGenerator, SqlGenerator,
playJsonScriptWriter, playJsonScriptWriter,
serializeJsTypesForJsonStringify,
} = require('dbgate-tools'); } = require('dbgate-tools');
const requireEngineDriver = require('../utility/requireEngineDriver'); const requireEngineDriver = require('../utility/requireEngineDriver');
const { connectUtility } = require('../utility/connectUtility'); const { connectUtility } = require('../utility/connectUtility');
@@ -232,7 +233,7 @@ async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false)
try { try {
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver); if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
const res = await driver.query(dbhan, sql, { range }); const res = await driver.query(dbhan, sql, { range });
process.send({ msgtype: 'response', msgid, ...res }); process.send({ msgtype: 'response', msgid, ...serializeJsTypesForJsonStringify(res) });
} catch (err) { } catch (err) {
process.send({ process.send({
msgtype: 'response', msgtype: 'response',

View File

@@ -4,6 +4,7 @@ const fs = require('fs');
const _ = require('lodash'); const _ = require('lodash');
const { jsldir } = require('../utility/directories'); const { jsldir } = require('../utility/directories');
const { serializeJsTypesReplacer } = require('dbgate-tools');
class QueryStreamTableWriter { class QueryStreamTableWriter {
constructor(sesid = undefined) { constructor(sesid = undefined) {
@@ -38,7 +39,7 @@ class QueryStreamTableWriter {
row(row) { row(row) {
// console.log('ACCEPT ROW', row); // console.log('ACCEPT ROW', row);
this.currentStream.write(JSON.stringify(row) + '\n'); this.currentStream.write(JSON.stringify(row, serializeJsTypesReplacer) + '\n');
this.currentRowCount += 1; this.currentRowCount += 1;
if (!this.plannedStats) { if (!this.plannedStats) {

View File

@@ -20,6 +20,7 @@ export function getFilterValueExpression(value, dataType?) {
if (value === true) return 'TRUE'; if (value === true) return 'TRUE';
if (value === false) return 'FALSE'; if (value === false) return 'FALSE';
if (value.$oid) return `ObjectId("${value.$oid}")`; if (value.$oid) return `ObjectId("${value.$oid}")`;
if (value.$bigint) return value.$bigint;
if (value.type == 'Buffer' && Array.isArray(value.data)) { if (value.type == 'Buffer' && Array.isArray(value.data)) {
return '0x' + arrayToHexString(value.data); return '0x' + arrayToHexString(value.data);
} }

View File

@@ -2,14 +2,18 @@ import P from 'parsimmon';
import moment from 'moment'; import moment from 'moment';
import { Condition } from 'dbgate-sqltree'; import { Condition } from 'dbgate-sqltree';
import { interpretEscapes, token, word, whitespace } from './common'; import { interpretEscapes, token, word, whitespace } from './common';
import { hexStringToArray } from 'dbgate-tools'; import { hexStringToArray, parseNumberSafe } from 'dbgate-tools';
import { FilterBehaviour, TransformType } from 'dbgate-types'; import { FilterBehaviour, TransformType } from 'dbgate-types';
const binaryCondition = const binaryCondition =
(operator, numberDualTesting = false) => (operator, numberDualTesting = false) =>
value => { value => {
const numValue = parseFloat(value); const numValue = parseNumberSafe(value);
if (numberDualTesting && !isNaN(numValue)) { if (
numberDualTesting &&
// @ts-ignore
!isNaN(numValue)
) {
return { return {
conditionType: 'or', conditionType: 'or',
conditions: [ conditions: [
@@ -345,17 +349,17 @@ const createParser = (filterBehaviour: FilterBehaviour) => {
string1Num: () => string1Num: () =>
token(P.regexp(/"-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?"/, 1)) token(P.regexp(/"-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?"/, 1))
.map(Number) .map(parseNumberSafe)
.desc('numer quoted'), .desc('numer quoted'),
string2Num: () => string2Num: () =>
token(P.regexp(/'-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?'/, 1)) token(P.regexp(/'-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?'/, 1))
.map(Number) .map(parseNumberSafe)
.desc('numer quoted'), .desc('numer quoted'),
number: () => number: () =>
token(P.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/)) token(P.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/))
.map(Number) .map(parseNumberSafe)
.desc('number'), .desc('number'),
objectid: () => token(P.regexp(/ObjectId\(['"]?[0-9a-f]{24}['"]?\)/)).desc('ObjectId'), objectid: () => token(P.regexp(/ObjectId\(['"]?[0-9a-f]{24}['"]?\)/)).desc('ObjectId'),

View File

@@ -16,11 +16,17 @@ function isLike(value, test) {
return res; return res;
} }
function extractRawValue(value) {
if (value?.$bigint) return value.$bigint;
if (value?.$oid) return value.$oid;
return value;
}
export function evaluateCondition(condition: Condition, values) { export function evaluateCondition(condition: Condition, values) {
switch (condition.conditionType) { switch (condition.conditionType) {
case 'binary': case 'binary':
const left = evaluateExpression(condition.left, values); const left = extractRawValue(evaluateExpression(condition.left, values));
const right = evaluateExpression(condition.right, values); const right = extractRawValue(evaluateExpression(condition.right, values));
switch (condition.operator) { switch (condition.operator) {
case '=': case '=':
return left == right; return left == right;
@@ -50,10 +56,15 @@ export function evaluateCondition(condition: Condition, values) {
case 'or': case 'or':
return condition.conditions.some(cond => evaluateCondition(cond, values)); return condition.conditions.some(cond => evaluateCondition(cond, values));
case 'like': case 'like':
return isLike(evaluateExpression(condition.left, values), evaluateExpression(condition.right, values)); return isLike(
break; extractRawValue(evaluateExpression(condition.left, values)),
extractRawValue(evaluateExpression(condition.right, values))
);
case 'notLike': case 'notLike':
return !isLike(evaluateExpression(condition.left, values), evaluateExpression(condition.right, values)); return !isLike(
extractRawValue(evaluateExpression(condition.left, values)),
extractRawValue(evaluateExpression(condition.right, values))
);
case 'not': case 'not':
return !evaluateCondition(condition.condition, values); return !evaluateCondition(condition.condition, values);
case 'anyColumnPass': case 'anyColumnPass':

View File

@@ -78,6 +78,7 @@ export class SqlDumper implements AlterProcessor {
else if (_isNumber(value)) this.putRaw(value.toString()); else if (_isNumber(value)) this.putRaw(value.toString());
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?.$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));
else this.put('^null'); else this.put('^null');
} }

View File

@@ -4,6 +4,7 @@ import _isDate from 'lodash/isDate';
import _isNumber from 'lodash/isNumber'; import _isNumber from 'lodash/isNumber';
import _isPlainObject from 'lodash/isPlainObject'; import _isPlainObject from 'lodash/isPlainObject';
import _pad from 'lodash/pad'; import _pad from 'lodash/pad';
import _cloneDeepWith from 'lodash/cloneDeepWith';
import { DataEditorTypesBehaviour } from 'dbgate-types'; import { DataEditorTypesBehaviour } from 'dbgate-types';
export type EditorDataType = export type EditorDataType =
@@ -208,6 +209,12 @@ export function stringifyCellValue(
} }
} }
} }
if (value?.$bigint) {
return {
value: value.$bigint,
gridStyle: 'valueCellStyle',
};
}
if (editorTypes?.parseDateAsDollar) { if (editorTypes?.parseDateAsDollar) {
if (value?.$date) { if (value?.$date) {
@@ -343,6 +350,9 @@ export function shouldOpenMultilineDialog(value) {
if (value?.$date) { if (value?.$date) {
return false; return false;
} }
if (value?.$bigint) {
return false;
}
if (_isPlainObject(value) || _isArray(value)) { if (_isPlainObject(value) || _isArray(value)) {
return true; return true;
} }
@@ -573,3 +583,44 @@ export function jsonLinesParse(jsonLines: string): any[] {
}) })
.filter(x => x); .filter(x => x);
} }
export function serializeJsTypesForJsonStringify(obj) {
return _cloneDeepWith(obj, value => {
if (typeof value === 'bigint') {
return { $bigint: value.toString() };
}
});
}
export function deserializeJsTypesFromJsonParse(obj) {
return _cloneDeepWith(obj, value => {
if (value?.$bigint) {
return BigInt(value.$bigint);
}
});
}
export function serializeJsTypesReplacer(key, value) {
if (typeof value === 'bigint') {
return { $bigint: value.toString() };
}
return value;
}
export function deserializeJsTypesReviver(key, value) {
if (value?.$bigint) {
return BigInt(value.$bigint);
}
return value;
}
export function parseNumberSafe(value) {
if (/^-?[0-9]+$/.test(value)) {
const parsed = parseInt(value);
if (Number.isSafeInteger(parsed)) {
return parsed;
}
return BigInt(value);
}
return parseFloat(value);
}

View File

@@ -54,7 +54,8 @@
$: style = computeStyle(maxWidth, col); $: style = computeStyle(maxWidth, col);
$: isJson = _.isPlainObject(value) && !(value?.type == 'Buffer' && _.isArray(value.data)) && !value.$oid; $: isJson =
_.isPlainObject(value) && !(value?.type == 'Buffer' && _.isArray(value.data)) && !value.$oid && !value.$bigint;
// don't parse JSON for explicit data types // don't parse JSON for explicit data types
$: jsonParsedValue = !editorTypes?.explicitDataType && isJsonLikeLongString(value) ? safeJsonParse(value) : null; $: jsonParsedValue = !editorTypes?.explicitDataType && isJsonLikeLongString(value) ? safeJsonParse(value) : null;

View File

@@ -72,6 +72,7 @@ export function countColumnSizes(grider: Grider, columns, containerWidth, displa
let text = value; let text = value;
if (_.isArray(value)) text = `[${value.length} items]`; if (_.isArray(value)) text = `[${value.length} items]`;
else if (value?.$oid) text = `ObjectId("${value.$oid}")`; else if (value?.$oid) text = `ObjectId("${value.$oid}")`;
else if (value?.$bigint) text = value.$bigint;
else if (isJsonLikeLongString(value) && safeJsonParse(value)) text = '(JSON)'; else if (isJsonLikeLongString(value) && safeJsonParse(value)) text = '(JSON)';
const width = context.measureText(text).width + 8; const width = context.measureText(text).width + 8;
// console.log('colName', colName, text, width); // console.log('colName', colName, text, width);

View File

@@ -13,6 +13,7 @@ import { callServerPing } from './connectionsPinger';
import { batchDispatchCacheTriggers, dispatchCacheChange } from './cache'; import { batchDispatchCacheTriggers, dispatchCacheChange } from './cache';
import { isAdminPage, isOneOfPage } from './pageDefs'; import { isAdminPage, isOneOfPage } from './pageDefs';
import { openWebLink } from './simpleTools'; import { openWebLink } from './simpleTools';
import { serializeJsTypesReplacer } from 'dbgate-tools';
export const strmid = uuidv1(); export const strmid = uuidv1();
@@ -177,7 +178,7 @@ export async function apiCall(
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...resolveApiHeaders(), ...resolveApiHeaders(),
}, },
body: JSON.stringify(args), body: JSON.stringify(args, serializeJsTypesReplacer),
}); });
if (resp.status == 401 && !apiDisabled) { if (resp.status == 401 && !apiDisabled) {

View File

@@ -21,6 +21,11 @@ const logger = getLogger('postreDriver');
pg.types.setTypeParser(1082, 'text', val => val); // date pg.types.setTypeParser(1082, 'text', val => val); // date
pg.types.setTypeParser(1114, 'text', val => val); // timestamp without timezone pg.types.setTypeParser(1114, 'text', val => val); // timestamp without timezone
pg.types.setTypeParser(1184, 'text', val => val); // timestamp pg.types.setTypeParser(1184, 'text', val => val); // timestamp
pg.types.setTypeParser(20, 'text', val => {
const parsed = parseInt(val);
if (Number.isSafeInteger(parsed)) return parsed;
return BigInt(val);
}); // timestamp
function extractGeographyDate(value) { function extractGeographyDate(value) {
try { try {