@evidence-dev/trino
Version:
Trino driver for Evidence projects
206 lines (190 loc) • 5.09 kB
JavaScript
const trino = require('presto-client');
const { EvidenceType, TypeFidelity } = require('@evidence-dev/db-commons');
const runQuery = async (queryString, database) => {
try {
const ssl = database.ssl;
const engine = database.engine;
const client = new trino.Client({
host: database.host,
ssl: ssl === 'true' || ssl === true ? {} : undefined,
port: database.port,
user: database.user,
source: 'evidence',
basic_auth: database.password ? auth(database.user, database.password) : undefined,
catalog: database.catalog,
schema: database.schema,
timeout: null,
engine: engine ? engine : 'trino'
});
const result = await new Promise((resolve, reject) => {
let rows = [];
let columnTypes = [];
client.execute({
query: queryString,
columns: function (_error, newColumnTypes) {
columnTypes = columnTypes.concat(newColumnTypes);
},
data: function (_error, newRows) {
rows = rows.concat(newRows);
},
callback: function (error) {
error === null ? resolve({ rows, columnTypes }) : reject({ error });
}
});
});
const adjustedResult = adjustResult(result);
return adjustedResult;
} catch (err) {
if (err.error?.message) {
throw err.error.message.replace(/\n|\r/g, ' ');
} else {
throw err;
}
}
};
const auth = (user, password) => {
return user !== null && password !== null ? { user, password } : null;
};
const adjustResult = (result) => {
const columnTypes = result.columnTypes.map((c) => {
return {
name: normalizeColumnName(c.name),
evidenceType: mapTrinoTypeToEvidenceType(c.type),
typeFidelity: TypeFidelity.PRECISE
};
});
const rows = result.rows.map((row) => {
const rowObj = {};
row.forEach((v, i) => {
let vAdjusted = typeof v === 'object' && v !== null ? v.toString() : v;
rowObj[columnTypes[i].name] = vAdjusted;
});
return rowObj;
});
return { rows, columnTypes };
};
const normalizeColumnName = (name) => {
return name.toLowerCase().replaceAll(' ', '_');
};
const mapTrinoTypeToEvidenceType = (type) => {
const typeLower = type.toLowerCase();
// Trino's types as reported by the API can be found here:
// https://github.com/trinodb/trino/blob/master/client/trino-client/src/main/java/io/trino/client/ClientStandardTypes.java
switch (typeLower) {
case 'boolean':
return EvidenceType.BOOLEAN;
case 'tinyint':
case 'smallint':
case 'integer':
case 'bigint':
case 'real':
case 'double':
return EvidenceType.NUMBER;
case typeLower.match(/^decimal/)?.input: // TODO: treat decimal as a number too?
case 'varchar':
case typeLower.match(/^char/)?.input:
case 'varbinary':
case 'json':
return EvidenceType.STRING;
case 'date':
case 'time':
case 'time with time zone':
case 'timestamp':
case 'timestamp with time zone':
return EvidenceType.DATE;
case 'interval year to month':
case 'interval day to second':
case typeLower.match(/^array/)?.input:
case typeLower.match(/^map/)?.input:
case typeLower.match(/^row/)?.input:
case 'ipaddress':
case 'uuid':
case 'hyperloglog':
case 'p4hyperloglog':
case typeLower.match(/^qdigest/)?.input:
case 'geometry':
case 'sphericalgeography':
return EvidenceType.STRING;
default:
return undefined;
}
};
module.exports = runQuery;
/**
* @typedef {Object} TrinoOptions
* @property {string} host
* @property {'true' | 'false'} ssl
* @property {string} port
* @property {string} user
* @property {string} password
* @property {string} catalog
* @property {string} schema
* @property {string} engine
*/
/** @type {import('@evidence-dev/db-commons').GetRunner<DatabricksOptions>} */
module.exports.getRunner = async (opts) => {
return async (queryContent, queryPath, batchSize) => {
// Filter out non-sql files
if (!queryPath.endsWith('.sql')) return null;
return runQuery(queryContent, opts, batchSize);
};
};
/** @type {import('@evidence-dev/db-commons').ConnectionTester<DatabricksOptions>} */
module.exports.testConnection = async (opts) => {
return await runQuery('SELECT 1', opts)
.then(() => true)
.catch((e) => ({ reason: e.message ?? (e.toString() || 'Invalid Credentials') }));
};
module.exports.options = {
host: {
title: 'Host',
type: 'string',
secret: false,
default: 'localhost',
required: true
},
ssl: {
title: 'Enable SSL',
type: 'boolean',
secret: false,
description: 'Whether or not to use SSL. Must be set to true for HTTPS.'
},
port: {
title: 'Port',
type: 'number',
secret: false,
default: 443,
required: true
},
user: {
title: 'Username',
type: 'string',
secret: false,
required: true
},
password: {
title: 'Password',
type: 'string',
secret: true,
required: false
},
catalog: {
title: 'Catalog',
type: 'string',
required: false,
secret: false
},
schema: {
title: 'Schema',
type: 'string',
secret: false,
required: false
},
engine: {
title: 'Engine',
type: 'string',
secret: false,
required: false,
description: 'The engine to use. Options are "trino" and "presto". Default is "trino".'
}
};