import axios from 'axios';
import dayjs from 'dayjs';
import isoWeek from 'dayjs/plugin/isoWeek';
import quarterOfYear from 'dayjs/plugin/quarterOfYear';
dayjs.extend(isoWeek);
dayjs.extend(quarterOfYear);
let ObjUtils = {
firstKey(obj) {
let k = Object.keys(obj);
return k.length ? k[0] : null;
},
replaceKey(obj, keyOld, keyNew) {
obj[keyNew] = obj[keyOld];
if (keyOld != keyNew) {
delete obj[keyOld];
}
return obj;
}
};
/**
* @namespace
*/
let Dremio = {
/**
* @enum {String}
*/
DATASET_TYPE: {
/** @member {String} */
VIRTUAL: 'VIRTUAL',
/** @member {String} */
PROMOTED: 'PROMOTED',
/** @member {String} */
DIRECT: 'DIRECT'
},
/**
* @enum {String}
*/
CONTAINER_TYPE: {
/** @member {String} */
SPACE: 'SPACE',
/** @member {String} */
SOURCE: 'SOURCE',
/** @member {String} */
FOLDER: 'FOLDER',
/** @member {String} */
HOME: 'HOME'
},
/**
* @enum {String}
*/
JOB_STATUS: {
/** @member {String} */
NOT_SUBMITTED: 'NOT_SUBMITTED',
/** @member {String} */
STARTING: 'STARTING',
/** @member {String} */
RUNNING: 'RUNNING',
/** @member {String} */
COMPLETED: 'COMPLETED',
/** @member {String} */
CANCELED: 'CANCELED',
/** @member {String} */
FAILED: 'FAILED',
/** @member {String} */
CANCELLATION_REQUESTED: 'CANCELLATION_REQUESTED',
/** @member {String} */
ENQUEUED: 'ENQUEUED'
},
/**
* @enum {String}
*/
SOURCE_TYPE: {
/** @member {String} */
AMAZONELASTIC: 'Amazon Elasticsearch Service',
/** @member {String} */
REDSHIFT: 'Amazon Redshift',
/** @member {String} */
S3: 'Amazon S3',
/** @member {String} */
ADLS: 'Azure Data Lake Storage Gen1',
/** @member {String} */
AZURE_STORAGE: 'Azure Storage',
/** @member {String} */
ELASTIC: 'Elasticsearch',
/** @member {String} */
HDFS: 'HDFS',
/** @member {String} */
HIVE: 'Hive',
/** @member {String} */
MAPRFS: 'MapR-FS',
/** @member {String} */
MSSQL: 'Microsoft SQL Server',
/** @member {String} */
MONGO: 'MongoDB',
/** @member {String} */
MYSQL: 'MySQL',
/** @member {String} */
NAS: 'NAS',
/** @member {String} */
ORACLE: 'Oracle',
/** @member {String} */
POSTGRES: 'PostgreSQL'
}
};
/**
* @class
* @extends Error
*/
class SDKError extends Error {
constructor(message) {
super();
this.name = 'SDKError';
this.message = message;
}
}
/**
* @class
* @extends Error
*/
class SDKValidationError extends SDKError {
constructor(message) {
super(message);
this.name = 'SDKValidationError';
}
}
/**
* SDKConfig
* @typedef {Object} SDKConfig
* @property {String} [host=http://localhost] host сервера
* @property {Boolean} [replaceFilterValues=true] нужно ли заменять константы в значениях фильтров
* @property {String} [authToken=null] auth token
* @property {Boolean} [admin=false] admin mode
*/
/**
* IQuerySchema
* @typedef {Object} IQuerySchema
* @property {Array} $from
* @property {Object} $fields
* @property {Array} $metrics
* @property {Array} $dimensions
* @property {Array} $filters
* @property {Array} $sort
*/
/**
* DremioResult
* @typedef {Object} DremioResult
* @property {Number} rowCount кол-во записей
* @property {Array} schema схема данных
* @property {Array} rows данные
*/
/**
* DremioRootEntity
* @typedef {Object} DremioRootEntity
* @property {String} id
* @property {Array} path
* @property {String} type
* @property {String} containerType
*/
/**
* DremioContainerInfo
* @typedef {Object} DremioContainerInfo
* @property {String} id
* @property {Array} path
* @property {String} name
* @property {String} entityType
* @property {DremioContainerChildInfo[]} children
*/
/**
* DremioContainerChildInfo
* @typedef {Object} DremioContainerChildInfo
* @property {String} id
* @property {Array} path
* @property {String} type
* @property {String} datasetType
* @property {String} containerType
*/
/**
* DremioEntityInfo
* @typedef {Object} DremioEntityInfo
* @property {String} id
* @property {Array} path
* @property {String} type
* @property {String} entityType
* @property {Array} fields
*/
/**
* @typedef {Object} DremioPermissions
* @property {String} employeeId
* @property {Array<Object>} permissions
*/
/**
* @typedef {Object} DremioPermissionInfo
* @property {String} name
*/
/**
* @typedef {Promise} PromiseCancelable
* @property {Function} cancel
*/
/**
* @class
*/
class SDK {
/**
* Constructor
* @param {SDKConfig} [config=null] config
*/
constructor(config = {}) {
let defaults = {
host: 'http://localhost',
replaceFilterValues: true,
authToken: null,
admin: false
};
/**
* @property {Function}
*/
this.beforeRequest = null;
/**
* @property {SDKConfig}
*/
this.config = Object.assign({}, defaults, config);
this.axios = axios.create();
this._axiosResolveHandler = ({ data }) => {
if (data.error) {
throw new SDKError(data.error);
} else {
return data;
}
};
this._axiosRejectHandler = e => {
e.isCancel = axios.isCancel(e);
// not 2xx + { error:'' }
if (e.response && e.response.data.error) {
throw new SDKError(e.response.data.error);
} else {
throw e;
}
};
this._requests = [];
}
/**
* Возвращает список прав текущего пользователя (если задан authToken {@link SDK#config} и он валиден)
* @return {PromiseCancelable.<DremioPermissions, Error>}
*/
getPermissions() {
let s = this._cancelSource();
let p = this._hook(this.beforeRequest)
.then(() =>
this.axios.get(
`${this.config.host}/api/v1/permissions`,
this._getRequestConfig({ cancelToken: s.token })
)
)
.then(this._axiosResolveHandler, this._axiosRejectHandler);
return this._cancelable(p, s);
}
/**
* Возвращает информацию о правах
* @return {PromiseCancelable.<DremioPermissionInfo[], Error>}
*/
getPermissionsInfo() {
let s = this._cancelSource();
let p = this._hook(this.beforeRequest)
.then(() =>
this.axios.get(
`${this.config.host}/api/v1/permissions-info`,
this._getRequestConfig({ cancelToken: s.token })
)
)
.then(this._axiosResolveHandler, this._axiosRejectHandler);
return this._cancelable(p, s);
}
/**
* Возрвщает root сущности dremio
* @return {PromiseCancelable.<DremioRootEntity[], Error>}
*/
getRootEntities() {
let s = this._cancelSource();
let p = this._hook(this.beforeRequest)
.then(() =>
this.axios.get(
`${this.config.host}/api/v1/catalog`,
this._getRequestConfig({ cancelToken: s.token })
)
)
.then(this._axiosResolveHandler, this._axiosRejectHandler);
return this._cancelable(p, s);
}
/**
* Возвращает сущность dremio.CatalogEntity по path
* @param {Array} [path=null] путь
* @return {PromiseCancelable.<DremioContainerInfo | DremioEntityInfo, Error>}
*/
getEntityByPath(path) {
let s = this._cancelSource();
let r = { path };
let p = this._hook(this.beforeRequest)
.then(() =>
this.axios.post(
`${this.config.host}/api/v1/catalog/by-path`,
r,
this._getRequestConfig({ cancelToken: s.token })
)
)
.then(this._axiosResolveHandler, this._axiosRejectHandler);
return this._cancelable(p, s);
}
/**
* Выполняет запрос
* @param {IQuerySchema} query query
* @param {Number} [offset=0] offset
* @param {Number} [limit=10] limit
* @param {Boolean} [debug=false] debug
* @return {PromiseCancelable.<DremioResult, Error>}
*/
getData(query, offset = 0, limit = 10, debug = false) {
if (this.config.replaceFilterValues) {
query = Query.queryReplaceAllFilterValues(Query.clone(query));
}
let s = this._cancelSource();
let r = {
query,
limit,
offset
};
if (debug) {
r.debug = debug;
}
let p = this._hook(this.beforeRequest)
.then(() =>
this.axios.post(
`${this.config.host}/api/v1/data`,
r,
this._getRequestConfig({ cancelToken: s.token })
)
)
.then(this._axiosResolveHandler, this._axiosRejectHandler);
return this._cancelable(p, s);
}
/**
* Отменяет все активные запросы
*/
cancelActiveRequests() {
while (this._requests.length) {
let p = this._requests.pop();
p.cancel();
}
}
/**
* @private
* @param {Object} config
* @return {Object}
*/
_getRequestConfig(config) {
let configAddons = {
headers: this._getRequestHeaders()
};
return { ...config, ...configAddons };
}
/**
* @private
* @return {Object}
*/
_getRequestHeaders() {
let { authToken, admin } = this.config;
let headers = {};
if (authToken) {
headers['token'] = authToken;
}
if (admin) {
headers['x-dremio-admin'] = 1;
}
return headers;
}
/**
* @private
* @param {Function} func
* @return {Promise}
*/
_hook(func) {
return func ? Promise.resolve(func()) : Promise.resolve();
}
/**
* @private
* @param {Promise} promise
* @param {CancelTokenSource} source
* @return {PromiseCancelable}
*/
_cancelable(promise, source) {
let remove = () => this._removeRequest(p);
let p = promise.finally(remove);
p.cancel = () => {
source.cancel('cancel');
remove();
};
this._addRequest(p);
return p;
}
/**
* @private
* @return {CancelTokenSource}
*/
_cancelSource() {
return axios.CancelToken.source();
}
/**
* @private
* @param {PromiseCancelable} promise
*/
_addRequest(promise) {
this._requests.push(promise);
}
/**
* @private
* @param {PromiseCancelable} promise
*/
_removeRequest(promise) {
this._requests = this._requests.filter(p => p !== promise);
}
}
/**
* @class
*/
class Query {
/**
* @typedef {Object} QueryOptions
* @property {IQuerySchema} query query
* @property {Object} dimensionList dimensionList
*/
/**
* Constructor
* @param {QueryOptions} options
*/
constructor({ query, dimensionList }) {
this.query = query;
this.dimensionList = dimensionList;
this.states = [];
this.enableDimensions(Object.keys(dimensionList));
}
/**
* Активирует dimension из списка dimensionList
* @param {Array} names массив названий dimension
*/
enableDimensions(names) {
let arr = [];
names.forEach(name => {
let fields = this.dimensionList[name];
if (!fields) {
return;
}
arr.push({
name,
index: 0,
fields,
filters: []
});
});
this.states = arr;
}
/**
* Формирует query с текущим state всех активных dimension
* @return {IQuerySchema} query
*/
buildQuery() {
let query = Query.clone(this.query);
this.states.forEach(({ name, index, fields, filters }) => {
let field = fields[index];
if (!field) {
return;
}
let dimension = Query.createDimension({ name, field });
Query.queryInsertUpdateDimension(query, dimension);
filters.forEach(filter => {
Query.queryInsertUpdateFilter(query, filter);
});
});
return query;
}
/**
* Проверяет, сущ. ли state для dimensionName
* @param {String} dimensionName название dimension
* @return {Boolean}
*/
dimensionStateExists(dimensionName) {
return this.states.find(({ name }) => name == dimensionName) != null;
}
/**
* Возвращает true, если dimension state первый
* @param {String} dimensionName название dimension
* @return {Boolean}
*/
dimensionStateIsFirst(dimensionName) {
let state = this.states.find(({ name }) => name == dimensionName);
return state ? state.index == 0 : false;
}
/**
* Возвращает true, если dimension state последний
* @param {String} dimensionName название dimension
* @return {Boolean}
*/
dimensionStateIsLast(dimensionName) {
let state = this.states.find(({ name }) => name == dimensionName);
return state ? state.index == state.fields.length - 1 : false;
}
/**
* Сдвигает тек. dimension state на шаг вперед
* @param {String} dimensionName название dimension
* @param {Array} filterValue значения filter
* @param {String} filterType тип filter {@link Query#FILTER_TYPE}
* @return {Boolean} true если удалось; false нет
*/
dimensionStateGoNext(dimensionName, filterValue, filterType) {
let state = this.states.find(({ name }) => dimensionName == name);
if (!state || state.index >= state.fields.length - 1) {
return false;
}
// add filter
let filter = Query.createFilter({
name: state.fields[state.index],
type: filterType,
value: filterValue
});
state.filters.push(filter);
state.index++;
}
/**
* Сдвигает тек. dimension state на шаг назад
* @param {String} dimensionName название dimension
* @return {Boolean} true если удалось; false нет
*/
dimensionStateGoPrev(dimensionName) {
let state = this.states.find(({ name }) => dimensionName == name);
if (!state || state.index <= 0) {
return false;
}
// remove filter
state.filters.pop();
state.index--;
return true;
}
/**
* Клонирует json-like obj
* @param {Object} obj
* @return {Object}
*/
static clone(obj) {
try {
return JSON.parse(JSON.stringify(obj));
} catch (e) {
return Object.assign({}, obj);
}
}
/**
* Создает/валидирует query
* @param {IQuerySchema} [query=null] query для валидации
*/
static createQuery(query = null) {
if (query) {
if (!Query.validateQuery(query)) {
throw new SDKValidationError('query schema invalid');
}
if (!Object.keys(query[Query.KEY.FIELDS])) {
throw new SDKValidationError(`query ${Query.KEY.FIELDS} schema invalid`);
}
if (!Query.validateFrom(query)) {
throw new SDKValidationError(`query ${Query.KEY.FROM} schema invalid`);
}
query[Query.KEY.METRICS].forEach(el => {
if (!Query.validateMetric(el)) {
throw new SDKValidationError(`query ${Query.KEY.METRICS} schema invalid`);
}
});
query[Query.KEY.DIMENSIONS].forEach(el => {
if (!Query.validateDimension(el)) {
throw new SDKValidationError(`query ${Query.KEY.DIMENSIONS} schema invalid`);
}
});
query[Query.KEY.FILTERS].forEach(el => {
if (!Query.validateFilter(el)) {
throw new SDKValidationError(`query ${Query.KEY.FILTERS} schema invalid`);
}
});
query[Query.KEY.SORT].forEach(el => {
if (!Query.validateSort(el)) {
throw new SDKValidationError(`query ${Query.KEY.SORT} schema invalid`);
}
});
} else {
query = {
[Query.KEY.FIELDS]: {},
[Query.KEY.FROM]: [],
[Query.KEY.METRICS]: [],
[Query.KEY.DIMENSIONS]: [],
[Query.KEY.FILTERS]: [],
[Query.KEY.SORT]: []
};
}
return query;
}
/**
* @typedef {Object} MetricOptions
* @param {String} name? название metric
* @param {String} type? тип metric {@link Query#METRIC_TYPE}
* @param {String} field? название field
*/
/**
* Создает/мутирует metric
* @param {MetricOptions} options опции
* @param {Object} [metric=null] metric для мутации
*/
static createMetric({ name, type, field }, metric = null) {
if (metric) {
// @TODO validate ~ throw new SDKValidationError()
if (name) {
ObjUtils.replaceKey(metric, ObjUtils.firstKey(metric), name);
}
if (type) {
let n = ObjUtils.firstKey(metric);
ObjUtils.replaceKey(metric[n], ObjUtils.firstKey(metric[n]), type);
}
if (field) {
let n = ObjUtils.firstKey(metric);
let t = ObjUtils.firstKey(metric[n]);
metric[n][t] = field;
}
} else {
metric = { [name]: { [type]: field } };
}
return metric;
}
/**
* @typedef {Object} DimensionOptions
* @param {String} name? название metric
* @param {String} field? название field
*/
/**
* Создает/мутирует dimension
* @param {DimensionOptions} options опции
* @param {Object} [dimension=null] dimension для мутации
*/
static createDimension({ name, field }, dimension = null) {
if (dimension) {
// @TODO validate ~ throw new SDKValidationError()
if (name) {
ObjUtils.replaceKey(dimension, ObjUtils.firstKey(dimension), name);
}
if (field) {
let n = ObjUtils.firstKey(dimension);
dimension[n] = field;
}
} else {
dimension = { [name]: field };
}
return dimension;
}
/**
* @typedef {Object} FilterOptions
* @param {String} name? название metric/dimension/field
* @param {String} type? тип filter {@link Query#FILTER_TYPE}
* @param {Array} value? массив значений
*/
/**
* Создает/мутирует filter
* @param {FilterOptions} options опции
* @param {Object} [filter=null] filter для мутации
*/
static createFilter({ name, type, value }, filter = null) {
if (filter) {
// @TODO validate ~ throw new SDKValidationError()
if (name) {
ObjUtils.replaceKey(filter, ObjUtils.firstKey(filter), name);
}
if (type) {
let n = ObjUtils.firstKey(filter);
ObjUtils.replaceKey(filter[n], ObjUtils.firstKey(filter[n]), type);
}
if (value) {
let n = ObjUtils.firstKey(filter);
let t = ObjUtils.firstKey(filter[n]);
filter[n][t] = value;
}
} else {
filter = { [name]: { [type]: value } };
}
return filter;
}
/**
* @typedef {Object} SortOptions
* @param {String} name? название metric/dimension
* @param {String} type? тип sort {@link Query#SORT_TYPE}
*/
/**
* Создает/мутирует sort
* @param {SortOptions} options опции
* @param {Object} [sort=null] sort для мутации
*/
static createSort({ name, type }, sort = null) {
if (sort) {
// @TODO validate ~ throw new SDKValidationError()
if (name) {
ObjUtils.replaceKey(sort, ObjUtils.firstKey(sort), name);
}
if (type) {
let n = ObjUtils.firstKey(sort);
sort[n] = type;
}
} else {
sort = { [name]: type };
}
return sort;
}
/**
* Возвращает имя metric
* @param {Object} metric
* @return {String}
*/
static getMetricName(metric) {
return ObjUtils.firstKey(metric);
}
/**
* Возвращает тип metric
* @param {Object} metric
* @return {String}
*/
static getMetricType(metric) {
let n = ObjUtils.firstKey(metric);
return ObjUtils.firstKey(metric[n]);
}
/**
* Возвращает field metric
* @param {Object} metric
* @return {String}
*/
static getMetricField(metric) {
let n = ObjUtils.firstKey(metric);
let t = ObjUtils.firstKey(metric[n]);
return metric[n][t];
}
/**
* Возвращает имя dimension
* @param {Object} dimension
* @return {String}
*/
static getDimensionName(dimension) {
return ObjUtils.firstKey(dimension);
}
/**
* Возвращает field dimension
* @param {Object} dimension
* @return {String}
*/
static getDimensionField(dimension) {
let n = ObjUtils.firstKey(dimension);
return dimension[n];
}
/**
* Возвращает имя filter
* @param {Object} filter
* @return {String}
*/
static getFilterName(filter) {
return ObjUtils.firstKey(filter);
}
/**
* Возвращает тип filter
* @param {Object} filter
* @return {String}
*/
static getFilterType(filter) {
let n = ObjUtils.firstKey(filter);
return ObjUtils.firstKey(filter[n]);
}
/**
* Возвращает value filter
* @param {Object} filter
* @return {String}
*/
static getFilterValue(filter) {
let n = ObjUtils.firstKey(filter);
let t = ObjUtils.firstKey(filter[n]);
return filter[n][t];
}
/**
* Возвращает имя sort
* @param {Object} sort
* @return {String}
*/
static getSortName(sort) {
return ObjUtils.firstKey(sort);
}
/**
* Возвращает тип sort
* @param {Object} sort
* @return {String}
*/
static getSortType(sort) {
let n = ObjUtils.firstKey(sort);
return sort[n];
}
/**
* Добавляет/заменяет filter в query
* @param {IQuerySchema} query query
* @param {Object} filter filter
* @return {IQuerySchema}
*/
static queryInsertUpdateFilter(query, filter) {
let arr = query[Query.KEY.FILTERS];
let filterName = Query.getFilterName(filter);
let index = arr.findIndex(el => Query.getFilterName(el) == filterName);
if (index >= 0) {
arr.splice(index, 1, filter);
} else {
arr.push(filter);
}
return query;
}
/**
* Удаляет filter из query
* @param {IQuerySchema} query query
* @param {String} name название metric/dimension
* @return {IQuerySchema}
*/
static queryRemoveFilter(query, name) {
query[Query.KEY.FILTERS] = query[Query.KEY.FILTERS].filter(
el => Query.getFilterName(el) != name
);
return query;
}
/**
* Удаляет все filter из query
* @param {IQuerySchema} query query
* @return {IQuerySchema}
*/
static queryRemoveAllFilters(query) {
query[Query.KEY.FILTERS] = [];
return query;
}
/**
* Заменяет Query.FILTER_VALUE value всех фильтров
* @param {IQuerySchema} query
* @return {IQuerySchema}
*/
static queryReplaceAllFilterValues(query) {
query[Query.KEY.FILTERS].forEach(el => {
let value = Query.getFilterValue(el).map(val => {
for (let key in Query.FILTER_VALUE) {
let handler = Query.FILTER_VALUE_HANDLER[key];
if (Query.FILTER_VALUE[key] === val && handler) {
return handler();
}
}
return val;
});
Query.createFilter({ value }, el);
});
return query;
}
/**
* Возвращает field name dimension
* @param {IQuerySchema} query query
* @param {String} name dimension name
* @return {String} field name или null если dimension нет
*/
static queryGetDimensionField(query, name) {
let dimension = query[Query.KEY.DIMENSIONS].find(el => Query.getDimensionName(el) == name);
return dimension ? dimension[name] : null;
}
/**
* Добавляет/заменяет dimension в query
* @param {IQuerySchema} query query
* @param {Object} dimension dimension
* @return {IQuerySchema}
*/
static queryInsertUpdateDimension(query, dimension) {
let arr = query[Query.KEY.DIMENSIONS];
let dimensionName = Query.getDimensionName(dimension);
let index = arr.findIndex(el => Query.getDimensionName(el) == dimensionName);
if (index >= 0) {
arr.splice(index, 1, dimension);
} else {
arr.push(dimension);
}
return query;
}
/**
* Возвращает массив имен field
* @param {IQuerySchema} query query
* @return {Array} массив имен field
*/
static queryFieldNames(query) {
return Object.keys(query[Query.KEY.FIELDS]);
}
/**
* Возвращает массив имен metric в query
* @param {IQuerySchema} query query
* @return {Array} массив имен metric
*/
static queryMetricNames(query) {
return query[Query.KEY.METRICS]
.map(el => Query.getMetricName(el))
.filter((v, i, arr) => arr.indexOf(v) === i);
}
/**
* Возвращает массив имен dimension в query
* @param {IQuerySchema} query query
* @return {Array} массив имен dimension
*/
static queryDimensionNames(query) {
return query[Query.KEY.DIMENSIONS]
.map(el => Query.getDimensionName(el))
.filter((v, i, arr) => arr.indexOf(v) === i);
}
/**
* Валидирует query
* @param {IQuerySchema} query
* @return {Boolean}
*/
static validateQuery(query) {
let keys = Object.values(Query.KEY);
let found = keys.reduce((acc, val) => (acc + query[val] ? 1 : 0), 0);
return keys.length != found;
}
/**
* Валидирует from
* @param {IQuerySchema} query
* @return {Boolean}
*/
static validateFrom(query) {
let from = query[Query.KEY.FROM];
return Array.isArray(from) && from.length > 0;
}
/**
* Валидирует metric
* @param {Object} metric
* @return {Boolean}
*/
static validateMetric(metric) {
if (!Query._validateObj(metric)) {
return false;
}
let name = ObjUtils.firstKey(metric);
let def = metric[name];
if (!def || !Query._validateObj(def)) {
return false;
}
let type = ObjUtils.firstKey(def);
if (Object.values(Query.METRIC_TYPE).indexOf(type) < 0) {
return false;
}
return typeof metric[name][type] == 'string' && metric[name][type].length > 0;
}
/**
* Валидирует dimension
* @param {Object} dimension
* @return {Boolean}
*/
static validateDimension(dimension) {
if (!Query._validateObj(dimension)) {
return false;
}
let name = ObjUtils.firstKey(dimension);
return typeof dimension[name] == 'string' && dimension[name].length > 0;
}
/**
* Валидирует filter
* @param {Object} filter
* @return {Boolean}
*/
static validateFilter(filter) {
if (!Query._validateObj(filter)) {
return false;
}
let name = ObjUtils.firstKey(filter);
let def = filter[name];
if (!def || !Query._validateObj(def)) {
return false;
}
let type = ObjUtils.firstKey(def);
if (Object.values(Query.FILTER_TYPE).indexOf(type) < 0) {
return false;
}
let values = filter[name][type];
if (!Array.isArray(values) || !Query.FILTER_TYPE_VALUE_VALIDATION[type](values)) {
return false;
}
return true;
}
/**
* Валидирует sort
* @param {Object} sort
* @return {Boolean}
*/
static validateSort(sort) {
if (!Query._validateObj(sort)) {
return false;
}
let name = ObjUtils.firstKey(sort);
if (Object.values(Query.SORT_TYPE).indexOf(sort[name]) < 0) {
return false;
}
return true;
}
/**
* @private Валидирует metric/dimension/filter/sort like obj
* @param {Object} obj
* @return {Boolean}
*/
static _validateObj(obj) {
if (typeof obj !== 'object') {
return false;
}
let k = ObjUtils.firstKey(obj);
if (!k) {
return false;
}
return true;
}
}
Query.FORMAT = {
DATE: 'YYYY-MM-DD',
TIME: 'HH:mm:ss',
TIMESTAMP: 'YYYY-MM-DD HH:mm:ss'
};
/**
* @static
* @enum {String}
*/
Query.KEY = {
/** @member {String} */
FIELDS: '$fields',
/** @member {String} */
FROM: '$from',
/** @member {String} */
METRICS: '$metrics',
/** @member {String} */
DIMENSIONS: '$dimensions',
/** @member {String} */
FILTERS: '$filters',
/** @member {String} */
SORT: '$sort'
};
/**
* @static
* @enum {String}
*/
Query.SORT_TYPE = {
/** @member {String} */
ASC: 'asc',
/** @member {String} */
DESC: 'desc'
};
/**
* @static
* @enum {String}
*/
Query.METRIC_TYPE = {
/**
* no aggregation
* @member {String}
*/
VALUE: '$v',
/**
* sql expression
* @member {String}
*/
EXPRESSION: '$exp',
/**
* equivalent of sql 'avg()'
* @member {String}
*/
AVG: '$avg',
/**
* equivalent of sql 'count()'
* @member {String}
*/
COUNT: '$count',
/**
* equivalent of sql 'max()'
* @member {String}
*/
MAX: '$max',
/**
* equivalent of sql 'min()'
* @member {String}
*/
MIN: '$min',
/**
* equivalent of sql 'sum()'
* @member {String}
*/
SUM: '$sum',
/**
* equivalent of mysql 'group_concat()'
* @member {String}
*/
GROUP_CONCAT: '$gc',
/**
* equivalent of mysql 'group_concat(distinct)'
* @member {String}
*/
GROUP_CONCAT_UNIQ: '$gcu'
};
/**
* @static
* @enum {String}
*/
Query.FILTER_TYPE = {
/**
* equivalent of sql '='
* @member {String}
*/
EQ: '$eq',
/**
* equivalent of sql '!='
* @member {String}
*/
EQ_NOT: '$eqn',
/**
* equivalent of sql '<'
* @member {String}
*/
LESS: '$lt',
/**
* equivalent of sql '<='
* @member {String}
*/
LESS_EQ: '$ltq',
/**
* equivalent of sql '>'
* @member {String}
*/
GREATER: '$gt',
/**
* equivalent of sql '>='
* @member {String}
*/
GREATER_EQ: '$gtq',
/**
* equivalent of sql 'in()'
* @member {String}
*/
IN: '$in',
/**
* equivalent of sql 'not in()'
* @member {String}
*/
IN_NOT: '$inn',
/**
* equivalent of sql 'between()'
* @member {String}
*/
BETWEEN: '$btw',
/**
* equivalent of sql 'not between()'
* @member {String}
*/
BETWEEN_NOT: '$btwn',
/**
* equivalent of sql 'like()'
* @member {String}
*/
LIKE: '$lk',
/**
* equivalent of sql 'max()'
* @member {String}
*/
MAX: '$max',
/**
* equivalent of sql 'min()'
* @member {String}
*/
MIN: '$min'
};
/**
* @static
* @enum {String}
*/
Query.FILTER_VALUE = {
/** @member {String} */
YEAR_ROLLING: '#year-rolling#',
/** @member {String} */
YEAR_START: '#year-start#',
/** @member {String} */
YEAR_END: '#year-end#',
/** @member {String} */
YEAR_PREV_START: '#year-prev-start#',
/** @member {String} */
YEAR_PREV_END: '#year-prev-end#',
/** @member {String} */
QUARTER_START: '#quarter-start#',
/** @member {String} */
QUARTER_END: '#quarter-end#',
/** @member {String} */
QUARTER_PY_START: '#quarter-py-start#',
/** @member {String} */
QUARTER_PY_END: '#quarter-py-end#',
/** @member {String} */
QUARTER_PREV_START: '#quarter-prev-start#',
/** @member {String} */
QUARTER_PREV_END: '#quarter-prev-end#',
/** @member {String} */
MONTH_ROLLING: '#month-rolling#',
/** @member {String} */
MONTH_START: '#month-start#',
/** @member {String} */
MONTH_END: '#month-end#',
/** @member {String} */
MONTH_PY_START: '#month-py-start#',
/** @member {String} */
MONTH_PY_END: '#month-py-end#',
/** @member {String} */
MONTH_PREV_START: '#month-prev-start#',
/** @member {String} */
MONTH_PREV_END: '#month-prev-end#',
/** @member {String} */
WEEK_ROLLING: '#week-rolling#',
/** @member {String} */
WEEK_START: '#week-start#',
/** @member {String} */
WEEK_END: '#week-end#',
/** @member {String} */
WEEK_PY_START: '#week-py-start#',
/** @member {String} */
WEEK_PY_END: '#week-py-end#',
/** @member {String} */
WEEK_PREV_START: '#week-prev-start#',
/** @member {String} */
WEEK_PREV_END: '#week-prev-end#',
/** @member {String} */
TODAY: '#today#',
/** @member {String} */
YESTERDAY: '#yesterday#'
};
/**
* @static
* @enum {String}
*/
Query.FILTER_VALUE_HANDLER = {
/** @member {Function} */
YEAR_ROLLING: () => dayjs().add(-365, 'd').format(Query.FORMAT.DATE),
/** @member {Function} */
YEAR_START: () => dayjs().startOf('year').format(Query.FORMAT.DATE),
/** @member {Function} */
YEAR_END: () => dayjs().endOf('year').format(Query.FORMAT.DATE),
/** @member {Function} */
YEAR_PREV_START: () => dayjs().add(-1, 'y').startOf('year').format(Query.FORMAT.DATE),
/** @member {Function} */
YEAR_PREV_END: () => dayjs().add(-1, 'y').endOf('year').format(Query.FORMAT.DATE),
/** @member {Function} */
QUARTER_START: () => dayjs().startOf('Q').format(Query.FORMAT.DATE),
/** @member {Function} */
QUARTER_END: () => dayjs().endOf('Q').format(Query.FORMAT.DATE),
/** @member {Function} */
QUARTER_PY_START: () => dayjs().add(-1, 'y').startOf('Q').format(Query.FORMAT.DATE),
/** @member {Function} */
QUARTER_PY_END: () => dayjs().add(-1, 'y').endOf('Q').format(Query.FORMAT.DATE),
/** @member {Function} */
QUARTER_PREV_START: () => dayjs().add(-1, 'Q').startOf('Q').format(Query.FORMAT.DATE),
/** @member {Function} */
QUARTER_PREV_END: () => dayjs().add(-1, 'Q').endOf('Q').format(Query.FORMAT.DATE),
/** @member {Function} */
MONTH_ROLLING: () => dayjs().add(-30, 'd').format(Query.FORMAT.DATE),
/** @member {Function} */
MONTH_START: () => dayjs().startOf('month').format(Query.FORMAT.DATE),
/** @member {Function} */
MONTH_END: () => dayjs().endOf('month').format(Query.FORMAT.DATE),
/** @member {Function} */
MONTH_PY_START: () => dayjs().add(-1, 'y').startOf('month').format(Query.FORMAT.DATE),
/** @member {Function} */
MONTH_PY_END: () => dayjs().add(-1, 'y').endOf('month').format(Query.FORMAT.DATE),
/** @member {Function} */
MONTH_PREV_START: () => dayjs().add(-1, 'M').startOf('month').format(Query.FORMAT.DATE),
/** @member {Function} */
MONTH_PREV_END: () => dayjs().add(-1, 'M').endOf('month').format(Query.FORMAT.DATE),
/** @member {Function} */
WEEK_ROLLING: () => dayjs().add(-7, 'd').format(Query.FORMAT.DATE),
/** @member {Function} */
WEEK_START: () => dayjs().startOf('isoWeek').format(Query.FORMAT.DATE),
/** @member {Function} */
WEEK_END: () => dayjs().endOf('isoWeek').format(Query.FORMAT.DATE),
/** @member {Function} */
WEEK_PY_START: () => dayjs().add(-1, 'y').startOf('isoWeek').format(Query.FORMAT.DATE),
/** @member {Function} */
WEEK_PY_END: () => dayjs().add(-1, 'y').endOf('isoWeek').format(Query.FORMAT.DATE),
/** @member {Function} */
WEEK_PREV_START: () =>
dayjs().startOf('isoWeek').add(-1, 'd').startOf('isoWeek').format(Query.FORMAT.DATE),
/** @member {Function} */
WEEK_PREV_END: () =>
dayjs().startOf('isoWeek').add(-1, 'd').endOf('isoWeek').format(Query.FORMAT.DATE),
/** @member {Function} */
TODAY: () => dayjs().format(Query.FORMAT.DATE),
/** @member {Function} */
YESTERDAY: () => dayjs().add(-1, 'd').format(Query.FORMAT.DATE)
};
Query.FILTER_TYPE_VALUE_VALIDATION = {
[Query.FILTER_TYPE.EQ]: v => v.length == 1,
[Query.FILTER_TYPE.EQ_NOT]: v => v.length == 1,
[Query.FILTER_TYPE.LESS]: v => v.length == 1,
[Query.FILTER_TYPE.LESS_EQ]: v => v.length == 1,
[Query.FILTER_TYPE.GREATER]: v => v.length == 1,
[Query.FILTER_TYPE.GREATER_EQ]: v => v.length == 1,
[Query.FILTER_TYPE.IN]: v => v.length > 0,
[Query.FILTER_TYPE.IN_NOT]: v => v.length > 0,
[Query.FILTER_TYPE.BETWEEN]: v => v.length == 2,
[Query.FILTER_TYPE.BETWEEN_NOT]: v => v.length == 2,
[Query.FILTER_TYPE.LIKE]: v => v.length > 0,
[Query.FILTER_TYPE.MAX]: v => v.length == 1,
[Query.FILTER_TYPE.MIN]: v => v.length == 1
};
/**
* @namespace
*/
let Errors = {
/** @property {SDKError} */
SDKError,
/** @property {SDKValidationError} */
SDKValidationError
};
export { SDK, Query, Dremio, Errors };