Refactored queries.

This commit is contained in:
Mike Cao 2023-07-24 23:06:16 -07:00
parent e4bd314bd6
commit 4bd3ef8e12
19 changed files with 330 additions and 408 deletions

View file

@ -2,7 +2,6 @@ import { ClickHouse } from 'clickhouse';
import dateFormat from 'dateformat';
import debug from 'debug';
import { CLICKHOUSE } from 'lib/db';
import { getDynamicDataType } from './dynamicData';
import { WebsiteMetricFilter } from './types';
import { FILTER_COLUMNS } from './constants';
@ -62,45 +61,6 @@ function getDateFormat(date) {
return `'${dateFormat(date, 'UTC:yyyy-mm-dd HH:MM:ss')}'`;
}
function getEventDataFilterQuery(
filters: {
eventKey?: string;
eventValue?: string | number | boolean | Date;
}[] = [],
params: any,
) {
const query = filters.reduce((ac, cv, i) => {
const type = getDynamicDataType(cv.eventValue);
let value = cv.eventValue;
ac.push(`and (event_key = {eventKey${i}:String}`);
switch (type) {
case 'number':
ac.push(`and number_value = {eventValue${i}:UInt64})`);
break;
case 'string':
ac.push(`and string_value = {eventValue${i}:String})`);
break;
case 'boolean':
ac.push(`and string_value = {eventValue${i}:String})`);
value = cv ? 'true' : 'false';
break;
case 'date':
ac.push(`and date_value = {eventValue${i}:DateTime('UTC')})`);
break;
}
params[`eventKey${i}`] = cv.eventKey;
params[`eventValue${i}`] = value;
return ac;
}, []);
return query.join('\n');
}
function getFilterQuery(filters = {}, params = {}) {
const query = Object.keys(filters).reduce((arr, key) => {
const filter = filters[key];
@ -146,22 +106,7 @@ function parseFilters(filters: WebsiteMetricFilter = {}, params: any = {}) {
};
}
function formatField(field, type, value) {
switch (type) {
case 'date':
return getDateFormat(value);
default:
return field;
}
}
async function rawQuery<T>(sql, params = {}): Promise<T> {
const query = sql.replaceAll(/\{\{\w+:\w+}}/g, token => {
const [, field, type] = token.match(/\{\{(\w+):(\w+)}}/);
return formatField(field, type, params[field]);
});
async function rawQuery<T>(query: string, params: object = {}): Promise<T> {
if (process.env.LOG_QUERY) {
log('QUERY:\n', query);
log('PARAMETERS:\n', params);
@ -202,7 +147,6 @@ export default {
getDateFormat,
getFilterQuery,
getFunnelQuery,
getEventDataFilterQuery,
parseFilters,
findUnique,
findFirst,

View file

@ -35,3 +35,7 @@ export async function runQuery(queries) {
return queries[CLICKHOUSE]();
}
}
export function notImplemented() {
throw new Error('Not implemented.');
}

View file

@ -61,7 +61,7 @@ async function getProducer(): Promise<Producer> {
return producer;
}
function getDateFormat(date, format?): string {
function getDateFormat(date: Date, format?: string): string {
return dateFormat(date, format ? format : 'UTC:yyyy-mm-dd HH:MM:ss');
}

View file

@ -1,7 +1,6 @@
import prisma from '@umami/prisma-client';
import moment from 'moment-timezone';
import { MYSQL, POSTGRESQL, getDatabaseType } from 'lib/db';
import { getDynamicDataType } from './dynamicData';
import { FILTER_COLUMNS } from './constants';
const MYSQL_DATE_FORMATS = {
@ -20,20 +19,8 @@ const POSTGRESQL_DATE_FORMATS = {
year: 'YYYY-01-01',
};
function toUuid(): string {
const db = getDatabaseType(process.env.DATABASE_URL);
if (db === POSTGRESQL) {
return '::uuid';
}
if (db === MYSQL) {
return '';
}
}
function getAddMinutesQuery(field: string, minutes: number) {
const db = getDatabaseType(process.env.DATABASE_URL);
const db = getDatabaseType();
if (db === POSTGRESQL) {
return `${field} + interval '${minutes} minute'`;
@ -45,7 +32,7 @@ function getAddMinutesQuery(field: string, minutes: number) {
}
function getDateQuery(field: string, unit: string, timezone?: string): string {
const db = getDatabaseType(process.env.DATABASE_URL);
const db = getDatabaseType();
if (db === POSTGRESQL) {
if (timezone) {
@ -65,8 +52,8 @@ function getDateQuery(field: string, unit: string, timezone?: string): string {
}
}
function getTimestampInterval(field: string): string {
const db = getDatabaseType(process.env.DATABASE_URL);
function getTimestampIntervalQuery(field: string): string {
const db = getDatabaseType();
if (db === POSTGRESQL) {
return `floor(extract(epoch from max(${field}) - min(${field})))`;
@ -77,47 +64,6 @@ function getTimestampInterval(field: string): string {
}
}
function getEventDataFilterQuery(
filters: {
eventKey?: string;
eventValue?: string | number | boolean | Date;
}[],
params: any[],
) {
const query = filters.reduce((ac, cv) => {
const type = getDynamicDataType(cv.eventValue);
let value = cv.eventValue;
ac.push(`and (event_key = $${params.length + 1}`);
params.push(cv.eventKey);
switch (type) {
case 'number':
ac.push(`and number_value = $${params.length + 1})`);
params.push(value);
break;
case 'string':
ac.push(`and string_value = $${params.length + 1})`);
params.push(decodeURIComponent(cv.eventValue as string));
break;
case 'boolean':
ac.push(`and string_value = $${params.length + 1})`);
params.push(decodeURIComponent(cv.eventValue as string));
value = cv ? 'true' : 'false';
break;
case 'date':
ac.push(`and date_value = $${params.length + 1})`);
params.push(cv.eventValue);
break;
}
return ac;
}, []);
return query.join('\n');
}
function getFilterQuery(filters = {}, params = []): string {
const query = Object.keys(filters).reduce((arr, key) => {
const filter = filters[key];
@ -163,7 +109,7 @@ function getFunnelQuery(
and l0.referrer_path = $${i + initParamLength}
and l0.url_path = $${levelNumber + initParamLength}
and created_at between $2 and $3
and website_id = $1${toUuid()}
and website_id = $1
)`;
}
@ -197,27 +143,32 @@ function parseFilters(
};
}
async function rawQuery(query: string, params: never[] = []): Promise<any> {
const db = getDatabaseType(process.env.DATABASE_URL);
async function rawQuery(sql: string, data: object): Promise<any> {
const db = getDatabaseType();
const params = [];
if (db !== POSTGRESQL && db !== MYSQL) {
return Promise.reject(new Error('Unknown database.'));
}
const sql = db === MYSQL ? query.replace(/\$[0-9]+/g, '?') : query;
const query = sql?.replaceAll(/\{\{(\w+)(::\w+)?}}/g, (...args) => {
const [, name, type] = args;
return prisma.rawQuery(sql, params);
params.push(data[name]);
return db === MYSQL ? '?' : `$${params.length}${type ?? ''}`;
});
return prisma.rawQuery(query, params);
}
export default {
...prisma,
getAddMinutesQuery,
getDateQuery,
getTimestampInterval,
getTimestampIntervalQuery,
getFilterQuery,
getFunnelQuery,
getEventDataFilterQuery,
toUuid,
parseFilters,
rawQuery,
};

View file

@ -1,9 +0,0 @@
export function buildSql(query: string, parameters: object) {
const params = { ...parameters };
const sql = query.replaceAll(/\$[\w_]+/g, name => {
return name;
});
return { sql, params };
}