Backend half
This commit is contained in:
+284
@@ -0,0 +1,284 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
// Copyright (c) 2015-2024 MariaDB Corporation Ab
|
||||
|
||||
'use strict';
|
||||
|
||||
class BinaryEncoder {
|
||||
/**
|
||||
* Write (and escape) current parameter value to output writer
|
||||
*
|
||||
* @param out output writer
|
||||
* @param value current parameter
|
||||
* @param opts connection options
|
||||
* @param info connection information
|
||||
*/
|
||||
static writeParam(out, value, opts, info) {
|
||||
// GEOJSON are not checked, because change to null/Buffer on parameter validation
|
||||
switch (typeof value) {
|
||||
case 'boolean':
|
||||
out.writeInt8(value ? 0x01 : 0x00);
|
||||
break;
|
||||
case 'bigint':
|
||||
if (value >= 2n ** 63n) {
|
||||
out.writeLengthEncodedString(value.toString());
|
||||
} else {
|
||||
out.writeBigInt(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
// additional verification, to permit query without type,
|
||||
// like 'SELECT ?' returning same type of value
|
||||
if (Number.isInteger(value) && value >= -2147483648 && value < 2147483647) {
|
||||
out.writeInt32(value);
|
||||
break;
|
||||
}
|
||||
out.writeDouble(value);
|
||||
break;
|
||||
case 'string':
|
||||
out.writeLengthEncodedString(value);
|
||||
break;
|
||||
case 'object':
|
||||
if (Object.prototype.toString.call(value) === '[object Date]') {
|
||||
out.writeBinaryDate(value);
|
||||
} else if (Buffer.isBuffer(value)) {
|
||||
out.writeLengthEncodedBuffer(value);
|
||||
} else if (typeof value.toSqlString === 'function') {
|
||||
out.writeLengthEncodedString(String(value.toSqlString()));
|
||||
} else {
|
||||
out.writeLengthEncodedString(JSON.stringify(value));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
out.writeLengthEncodedBuffer(value);
|
||||
}
|
||||
}
|
||||
|
||||
static getBufferFromGeometryValue(value, headerType) {
|
||||
let geoBuff;
|
||||
let pos;
|
||||
let type;
|
||||
if (!headerType) {
|
||||
switch (value.type) {
|
||||
case 'Point':
|
||||
geoBuff = Buffer.allocUnsafe(21);
|
||||
geoBuff.writeInt8(0x01, 0); //LITTLE ENDIAN
|
||||
geoBuff.writeInt32LE(1, 1); //wkbPoint
|
||||
if (
|
||||
value.coordinates &&
|
||||
Array.isArray(value.coordinates) &&
|
||||
value.coordinates.length >= 2 &&
|
||||
!isNaN(value.coordinates[0]) &&
|
||||
!isNaN(value.coordinates[1])
|
||||
) {
|
||||
geoBuff.writeDoubleLE(value.coordinates[0], 5); //X
|
||||
geoBuff.writeDoubleLE(value.coordinates[1], 13); //Y
|
||||
return geoBuff;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
case 'LineString':
|
||||
if (value.coordinates && Array.isArray(value.coordinates)) {
|
||||
const pointNumber = value.coordinates.length;
|
||||
geoBuff = Buffer.allocUnsafe(9 + 16 * pointNumber);
|
||||
geoBuff.writeInt8(0x01, 0); //LITTLE ENDIAN
|
||||
geoBuff.writeInt32LE(2, 1); //wkbLineString
|
||||
geoBuff.writeInt32LE(pointNumber, 5);
|
||||
for (let i = 0; i < pointNumber; i++) {
|
||||
if (
|
||||
value.coordinates[i] &&
|
||||
Array.isArray(value.coordinates[i]) &&
|
||||
value.coordinates[i].length >= 2 &&
|
||||
!isNaN(value.coordinates[i][0]) &&
|
||||
!isNaN(value.coordinates[i][1])
|
||||
) {
|
||||
geoBuff.writeDoubleLE(value.coordinates[i][0], 9 + 16 * i); //X
|
||||
geoBuff.writeDoubleLE(value.coordinates[i][1], 17 + 16 * i); //Y
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return geoBuff;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
case 'Polygon':
|
||||
if (value.coordinates && Array.isArray(value.coordinates)) {
|
||||
const numRings = value.coordinates.length;
|
||||
let size = 0;
|
||||
for (let i = 0; i < numRings; i++) {
|
||||
size += 4 + 16 * value.coordinates[i].length;
|
||||
}
|
||||
geoBuff = Buffer.allocUnsafe(9 + size);
|
||||
geoBuff.writeInt8(0x01, 0); //LITTLE ENDIAN
|
||||
geoBuff.writeInt32LE(3, 1); //wkbPolygon
|
||||
geoBuff.writeInt32LE(numRings, 5);
|
||||
pos = 9;
|
||||
for (let i = 0; i < numRings; i++) {
|
||||
const lineString = value.coordinates[i];
|
||||
if (lineString && Array.isArray(lineString)) {
|
||||
geoBuff.writeInt32LE(lineString.length, pos);
|
||||
pos += 4;
|
||||
for (let j = 0; j < lineString.length; j++) {
|
||||
if (
|
||||
lineString[j] &&
|
||||
Array.isArray(lineString[j]) &&
|
||||
lineString[j].length >= 2 &&
|
||||
!isNaN(lineString[j][0]) &&
|
||||
!isNaN(lineString[j][1])
|
||||
) {
|
||||
geoBuff.writeDoubleLE(lineString[j][0], pos); //X
|
||||
geoBuff.writeDoubleLE(lineString[j][1], pos + 8); //Y
|
||||
pos += 16;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return geoBuff;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
case 'MultiPoint':
|
||||
type = 'MultiPoint';
|
||||
geoBuff = Buffer.allocUnsafe(9);
|
||||
geoBuff.writeInt8(0x01, 0); //LITTLE ENDIAN
|
||||
geoBuff.writeInt32LE(4, 1); //wkbMultiPoint
|
||||
break;
|
||||
|
||||
case 'MultiLineString':
|
||||
type = 'MultiLineString';
|
||||
geoBuff = Buffer.allocUnsafe(9);
|
||||
geoBuff.writeInt8(0x01, 0); //LITTLE ENDIAN
|
||||
geoBuff.writeInt32LE(5, 1); //wkbMultiLineString
|
||||
break;
|
||||
|
||||
case 'MultiPolygon':
|
||||
type = 'MultiPolygon';
|
||||
geoBuff = Buffer.allocUnsafe(9);
|
||||
geoBuff.writeInt8(0x01, 0); //LITTLE ENDIAN
|
||||
geoBuff.writeInt32LE(6, 1); //wkbMultiPolygon
|
||||
break;
|
||||
|
||||
case 'GeometryCollection':
|
||||
geoBuff = Buffer.allocUnsafe(9);
|
||||
geoBuff.writeInt8(0x01, 0); //LITTLE ENDIAN
|
||||
geoBuff.writeInt32LE(7, 1); //wkbGeometryCollection
|
||||
|
||||
if (value.geometries && Array.isArray(value.geometries)) {
|
||||
const coordinateLength = value.geometries.length;
|
||||
const subArrays = [geoBuff];
|
||||
for (let i = 0; i < coordinateLength; i++) {
|
||||
const tmpBuf = this.getBufferFromGeometryValue(value.geometries[i]);
|
||||
if (tmpBuf === null) break;
|
||||
subArrays.push(tmpBuf);
|
||||
}
|
||||
geoBuff.writeInt32LE(subArrays.length - 1, 5);
|
||||
return Buffer.concat(subArrays);
|
||||
} else {
|
||||
geoBuff.writeInt32LE(0, 5);
|
||||
return geoBuff;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
if (value.coordinates && Array.isArray(value.coordinates)) {
|
||||
const coordinateLength = value.coordinates.length;
|
||||
const subArrays = [geoBuff];
|
||||
for (let i = 0; i < coordinateLength; i++) {
|
||||
const tmpBuf = this.getBufferFromGeometryValue(value.coordinates[i], type);
|
||||
if (tmpBuf === null) break;
|
||||
subArrays.push(tmpBuf);
|
||||
}
|
||||
geoBuff.writeInt32LE(subArrays.length - 1, 5);
|
||||
return Buffer.concat(subArrays);
|
||||
} else {
|
||||
geoBuff.writeInt32LE(0, 5);
|
||||
return geoBuff;
|
||||
}
|
||||
} else {
|
||||
switch (headerType) {
|
||||
case 'MultiPoint':
|
||||
if (value && Array.isArray(value) && value.length >= 2 && !isNaN(value[0]) && !isNaN(value[1])) {
|
||||
geoBuff = Buffer.allocUnsafe(21);
|
||||
geoBuff.writeInt8(0x01, 0); //LITTLE ENDIAN
|
||||
geoBuff.writeInt32LE(1, 1); //wkbPoint
|
||||
geoBuff.writeDoubleLE(value[0], 5); //X
|
||||
geoBuff.writeDoubleLE(value[1], 13); //Y
|
||||
return geoBuff;
|
||||
}
|
||||
return null;
|
||||
|
||||
case 'MultiLineString':
|
||||
if (value && Array.isArray(value)) {
|
||||
const pointNumber = value.length;
|
||||
geoBuff = Buffer.allocUnsafe(9 + 16 * pointNumber);
|
||||
geoBuff.writeInt8(0x01, 0); //LITTLE ENDIAN
|
||||
geoBuff.writeInt32LE(2, 1); //wkbLineString
|
||||
geoBuff.writeInt32LE(pointNumber, 5);
|
||||
for (let i = 0; i < pointNumber; i++) {
|
||||
if (
|
||||
value[i] &&
|
||||
Array.isArray(value[i]) &&
|
||||
value[i].length >= 2 &&
|
||||
!isNaN(value[i][0]) &&
|
||||
!isNaN(value[i][1])
|
||||
) {
|
||||
geoBuff.writeDoubleLE(value[i][0], 9 + 16 * i); //X
|
||||
geoBuff.writeDoubleLE(value[i][1], 17 + 16 * i); //Y
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return geoBuff;
|
||||
}
|
||||
return null;
|
||||
|
||||
case 'MultiPolygon':
|
||||
if (value && Array.isArray(value)) {
|
||||
const numRings = value.length;
|
||||
let size = 0;
|
||||
for (let i = 0; i < numRings; i++) {
|
||||
size += 4 + 16 * value[i].length;
|
||||
}
|
||||
geoBuff = Buffer.allocUnsafe(9 + size);
|
||||
geoBuff.writeInt8(0x01, 0); //LITTLE ENDIAN
|
||||
geoBuff.writeInt32LE(3, 1); //wkbPolygon
|
||||
geoBuff.writeInt32LE(numRings, 5);
|
||||
pos = 9;
|
||||
for (let i = 0; i < numRings; i++) {
|
||||
const lineString = value[i];
|
||||
if (lineString && Array.isArray(lineString)) {
|
||||
geoBuff.writeInt32LE(lineString.length, pos);
|
||||
pos += 4;
|
||||
for (let j = 0; j < lineString.length; j++) {
|
||||
if (
|
||||
lineString[j] &&
|
||||
Array.isArray(lineString[j]) &&
|
||||
lineString[j].length >= 2 &&
|
||||
!isNaN(lineString[j][0]) &&
|
||||
!isNaN(lineString[j][1])
|
||||
) {
|
||||
geoBuff.writeDoubleLE(lineString[j][0], pos); //X
|
||||
geoBuff.writeDoubleLE(lineString[j][1], pos + 8); //Y
|
||||
pos += 16;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return geoBuff;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BinaryEncoder;
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
// Copyright (c) 2015-2024 MariaDB Corporation Ab
|
||||
|
||||
'use strict';
|
||||
|
||||
const QUOTE = 0x27;
|
||||
|
||||
// Cache common GeoJSON types
|
||||
const GEO_TYPES = new Set([
|
||||
'Point',
|
||||
'LineString',
|
||||
'Polygon',
|
||||
'MultiPoint',
|
||||
'MultiLineString',
|
||||
'MultiPolygon',
|
||||
'GeometryCollection'
|
||||
]);
|
||||
|
||||
// Optimized function to pad numbers with leading zeros
|
||||
const formatDigit = function (val, significantDigit) {
|
||||
const str = `${val}`;
|
||||
return str.length < significantDigit ? '0'.repeat(significantDigit - str.length) + str : str;
|
||||
};
|
||||
|
||||
class TextEncoder {
|
||||
/**
|
||||
* Write (and escape) current parameter value to output writer
|
||||
*
|
||||
* @param out output writer
|
||||
* @param value current parameter. Expected to be non-null
|
||||
* @param opts connection options
|
||||
* @param info connection information
|
||||
*/
|
||||
static writeParam(out, value, opts, info) {
|
||||
switch (typeof value) {
|
||||
case 'boolean':
|
||||
out.writeStringAscii(value ? 'true' : 'false');
|
||||
break;
|
||||
case 'bigint':
|
||||
case 'number':
|
||||
out.writeStringAscii(`${value}`);
|
||||
break;
|
||||
case 'string':
|
||||
out.writeStringEscapeQuote(value);
|
||||
break;
|
||||
case 'object':
|
||||
if (Object.prototype.toString.call(value) === '[object Date]') {
|
||||
out.writeStringAscii(TextEncoder.getLocalDate(value));
|
||||
} else if (Buffer.isBuffer(value)) {
|
||||
out.writeStringAscii("_BINARY '");
|
||||
out.writeBufferEscape(value);
|
||||
out.writeInt8(QUOTE);
|
||||
} else if (typeof value.toSqlString === 'function') {
|
||||
out.writeStringEscapeQuote(String(value.toSqlString()));
|
||||
} else if (Array.isArray(value)) {
|
||||
if (opts.arrayParenthesis) {
|
||||
out.writeStringAscii('(');
|
||||
}
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (i !== 0) out.writeStringAscii(',');
|
||||
if (value[i] == null) {
|
||||
out.writeStringAscii('NULL');
|
||||
} else TextEncoder.writeParam(out, value[i], opts, info);
|
||||
}
|
||||
|
||||
if (opts.arrayParenthesis) {
|
||||
out.writeStringAscii(')');
|
||||
}
|
||||
} else {
|
||||
if (value.type != null && GEO_TYPES.has(value.type)) {
|
||||
//GeoJSON format.
|
||||
const isMariaDb = info.isMariaDB();
|
||||
const prefix =
|
||||
(isMariaDb && info.hasMinVersion(10, 1, 4)) || (!isMariaDb && info.hasMinVersion(5, 7, 6)) ? 'ST_' : '';
|
||||
|
||||
switch (value.type) {
|
||||
case 'Point':
|
||||
out.writeStringAscii(
|
||||
prefix + "PointFromText('POINT(" + TextEncoder.geoPointToString(value.coordinates) + ")')"
|
||||
);
|
||||
break;
|
||||
|
||||
case 'LineString':
|
||||
out.writeStringAscii(
|
||||
prefix + "LineFromText('LINESTRING(" + TextEncoder.geoArrayPointToString(value.coordinates) + ")')"
|
||||
);
|
||||
break;
|
||||
|
||||
case 'Polygon':
|
||||
out.writeStringAscii(
|
||||
prefix +
|
||||
"PolygonFromText('POLYGON(" +
|
||||
TextEncoder.geoMultiArrayPointToString(value.coordinates) +
|
||||
")')"
|
||||
);
|
||||
break;
|
||||
|
||||
case 'MultiPoint':
|
||||
out.writeStringAscii(
|
||||
prefix +
|
||||
"MULTIPOINTFROMTEXT('MULTIPOINT(" +
|
||||
TextEncoder.geoArrayPointToString(value.coordinates) +
|
||||
")')"
|
||||
);
|
||||
break;
|
||||
|
||||
case 'MultiLineString':
|
||||
out.writeStringAscii(
|
||||
prefix +
|
||||
"MLineFromText('MULTILINESTRING(" +
|
||||
TextEncoder.geoMultiArrayPointToString(value.coordinates) +
|
||||
")')"
|
||||
);
|
||||
break;
|
||||
|
||||
case 'MultiPolygon':
|
||||
out.writeStringAscii(
|
||||
prefix +
|
||||
"MPolyFromText('MULTIPOLYGON(" +
|
||||
TextEncoder.geoMultiPolygonToString(value.coordinates) +
|
||||
")')"
|
||||
);
|
||||
break;
|
||||
|
||||
case 'GeometryCollection':
|
||||
out.writeStringAscii(
|
||||
prefix +
|
||||
"GeomCollFromText('GEOMETRYCOLLECTION(" +
|
||||
TextEncoder.geometricCollectionToString(value.geometries) +
|
||||
")')"
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else if (String === value.constructor) {
|
||||
out.writeStringEscapeQuote(value);
|
||||
break;
|
||||
} else {
|
||||
if (opts.permitSetMultiParamEntries) {
|
||||
let first = true;
|
||||
for (const key in value) {
|
||||
const val = value[key];
|
||||
if (typeof val === 'function') continue;
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
out.writeStringAscii(',');
|
||||
}
|
||||
|
||||
out.writeString('`' + key + '`');
|
||||
|
||||
if (val == null) {
|
||||
out.writeStringAscii('=NULL');
|
||||
} else {
|
||||
out.writeStringAscii('=');
|
||||
TextEncoder.writeParam(out, val, opts, info);
|
||||
}
|
||||
}
|
||||
if (first) out.writeStringEscapeQuote(JSON.stringify(value));
|
||||
} else {
|
||||
out.writeStringEscapeQuote(JSON.stringify(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static geometricCollectionToString(geo) {
|
||||
if (!geo) return '';
|
||||
|
||||
const len = geo.length;
|
||||
let st = '';
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const item = geo[i];
|
||||
//GeoJSON format.
|
||||
if (i !== 0) st += ',';
|
||||
|
||||
switch (item.type) {
|
||||
case 'Point':
|
||||
st += `POINT(${TextEncoder.geoPointToString(item.coordinates)})`;
|
||||
break;
|
||||
|
||||
case 'LineString':
|
||||
st += `LINESTRING(${TextEncoder.geoArrayPointToString(item.coordinates)})`;
|
||||
break;
|
||||
|
||||
case 'Polygon':
|
||||
st += `POLYGON(${TextEncoder.geoMultiArrayPointToString(item.coordinates)})`;
|
||||
break;
|
||||
|
||||
case 'MultiPoint':
|
||||
st += `MULTIPOINT(${TextEncoder.geoArrayPointToString(item.coordinates)})`;
|
||||
break;
|
||||
|
||||
case 'MultiLineString':
|
||||
st += `MULTILINESTRING(${TextEncoder.geoMultiArrayPointToString(item.coordinates)})`;
|
||||
break;
|
||||
|
||||
case 'MultiPolygon':
|
||||
st += `MULTIPOLYGON(${TextEncoder.geoMultiPolygonToString(item.coordinates)})`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
static geoMultiPolygonToString(coords) {
|
||||
if (!coords) return '';
|
||||
|
||||
const len = coords.length;
|
||||
if (len === 0) return '';
|
||||
|
||||
let st = '(';
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (i !== 0) st += ',(';
|
||||
st += TextEncoder.geoMultiArrayPointToString(coords[i]) + ')';
|
||||
}
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
static geoMultiArrayPointToString(coords) {
|
||||
if (!coords) return '';
|
||||
|
||||
const len = coords.length;
|
||||
if (len === 0) return '';
|
||||
|
||||
let st = '(';
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (i !== 0) st += ',(';
|
||||
st += TextEncoder.geoArrayPointToString(coords[i]) + ')';
|
||||
}
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
static geoArrayPointToString(coords) {
|
||||
if (!coords) return '';
|
||||
|
||||
const len = coords.length;
|
||||
if (len === 0) return '';
|
||||
|
||||
let st = '';
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (i !== 0) st += ',';
|
||||
st += TextEncoder.geoPointToString(coords[i]);
|
||||
}
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
static geoPointToString(coords) {
|
||||
if (!coords) return '';
|
||||
const x = isNaN(coords[0]) ? '' : coords[0];
|
||||
const y = isNaN(coords[1]) ? '' : coords[1];
|
||||
return x + ' ' + y;
|
||||
}
|
||||
|
||||
static getLocalDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
const seconds = date.getSeconds();
|
||||
const ms = date.getMilliseconds();
|
||||
|
||||
const d = "'" + year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
|
||||
|
||||
if (ms === 0) return d + "'";
|
||||
|
||||
return d + '.' + (ms < 10 ? '00' : ms < 100 ? '0' : '') + ms + "'";
|
||||
}
|
||||
|
||||
static getFixedFormatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const mon = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const hour = date.getHours();
|
||||
const min = date.getMinutes();
|
||||
const sec = date.getSeconds();
|
||||
const ms = date.getMilliseconds();
|
||||
|
||||
let result =
|
||||
"'" +
|
||||
formatDigit(year, 4) +
|
||||
'-' +
|
||||
formatDigit(mon, 2) +
|
||||
'-' +
|
||||
formatDigit(day, 2) +
|
||||
' ' +
|
||||
formatDigit(hour, 2) +
|
||||
':' +
|
||||
formatDigit(min, 2) +
|
||||
':' +
|
||||
formatDigit(sec, 2);
|
||||
|
||||
if (ms > 0) {
|
||||
result += '.' + formatDigit(ms, 3);
|
||||
}
|
||||
|
||||
return result + "'";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextEncoder;
|
||||
Reference in New Issue
Block a user