"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
exports.__esModule = true;
exports.genCountQuery = exports.aggregationFnBuilder = exports.toDeleteQuery = exports.toUpdateQuery = exports.toReadQuery = exports.toCreateQuery = exports.toSearchQuery = exports.sqlSchemaResource = void 0;
var cnst = require("./const");
var utils_1 = require("./utils");
/* tslint:disable */
// sqlite doesnt have schemas and as such this will break the knex querybuilder
exports.sqlSchemaResource = function (_a) {
    var resource_schema = _a.resource_schema, resource_name = _a.resource_name;
    return resource_schema === ""
        ? resource_name
        : resource_schema + "." + resource_name;
};
exports.toSearchQuery = function (_a) {
    var db = _a.db, st = _a.st, resource = _a.resource, components = _a.components, context = _a.context, schemaResource = _a.schemaResource, _b = _a.subqueryOptions, subquery = _b.subquery, aggregationFn = _b.aggregationFn, geoFields = _a.geoFields;
    var prefix = subquery ? "top_" : "";
    // oracle does not accept the `as` alias_name
    var alais_declaration = db.client.config.client == "oracledb" ? " " : " as ";
    var main = !!subquery
        ? db.raw("(" + subquery.toString() + ")" + alais_declaration + prefix + "main")
        : exports.sqlSchemaResource(schemaResource);
    // db_lookup: select or distinct
    var db_lookup = context.distinct ? db.distinct(context.fields) : db.select(context.fields);
    var base = db_lookup.from(main);
    var query = !aggregationFn
        ? base
        : db
            .select()
            .from(db.raw("(" + aggregationFn(base).toString() + ")" + alais_declaration + prefix + "complex"));
    return (query
        // notWhere where/notWhere
        // statementContext and/or
        .where(function (sql) {
        for (var _i = 0, components_1 = components; _i < components_1.length; _i++) {
            var _a = components_1[_i], field = _a.field, operation = _a.operation, value = _a.value;
            if (cnst.BASIC_QUERY_OPERATIONS.get(operation)) {
                sql.andWhere(field, cnst.BASIC_QUERY_OPERATIONS.get(operation), value);
            }
            else if (operation === cnst.RANGE) {
                sql.whereBetween(field, [value[0], value[1]]);
            }
            else if (operation === cnst.NOT_RANGE) {
                sql.whereNotBetween(field, [value[0], value[1]]);
            }
            else if (operation === cnst.IN) {
                sql.whereIn([field], value);
            }
            else if (operation === cnst.NOT_IN) {
                sql.whereNotIn([field], value);
            }
            else if (operation === cnst.NULL) {
                sql.whereNull(field);
            }
            else if (operation === cnst.NOT_NULL) {
                sql.whereNotNull(field);
            }
            else if (operation === cnst.GEO_BBOX) {
                // long, lat, long, lat | bottom-left, top-right | prob matters which hemisphere
                var _b = value.map(parseFloat), xMin = _b[0], yMin = _b[1], xMax = _b[2], yMax = _b[3];
                sql.andWhere(st.intersects(field, st.makeEnvelope(xMin, yMin, xMax, yMax, geoFields[field].srid)));
            }
            else if (operation === cnst.GEO_RADIUS) {
                // long lat to match order in bbox
                var _c = value.map(Number), long = _c[0], lat = _c[1], meters = _c[2];
                var coords = st.setSRID(st.makePoint(long, lat), geoFields[field].srid);
                sql.andWhere(st.dwithin(field, coords, utils_1.metersToDecimalDegrees(meters)));
            }
            else if (operation === cnst.GEO_POLYGON) {
                // TODO: eval this
                sql.andWhere(st.intersects(field, st.geomFromText(value, geoFields[field].srid)));
            }
        }
        return sql;
    })
        .orderBy(context.orderBy || [])
        .limit(context.limit)
        .offset(((context.page || 1) - 1) * (context.limit || 100)));
};
exports.toCreateQuery = function (_a) {
    var db = _a.db, st = _a.st, resource = _a.resource, query = _a.query, context = _a.context, schemaResource = _a.schemaResource, supportsReturn = _a.supportsReturn;
    return db
        .insert(query, supportsReturn ? context.fields : undefined)
        .into(exports.sqlSchemaResource(schemaResource)); // fields exists. was set in generic
};
exports.toReadQuery = function (_a) {
    var db = _a.db, st = _a.st, resource = _a.resource, query = _a.query, context = _a.context, schemaResource = _a.schemaResource;
    return db
        .from(exports.sqlSchemaResource(schemaResource))
        .select(context.fields)
        .where(query);
}; // fields exists. was set in generic
exports.toUpdateQuery = function (keys) {
    return function (_a) {
        var db = _a.db, st = _a.st, resource = _a.resource, query = _a.query, context = _a.context, searchQuery = _a.searchQuery, schemaResource = _a.schemaResource, supportsReturn = _a.supportsReturn;
        var _b = Object.entries(query).reduce(function (bundle, _a) {
            var key = _a[0], value = _a[1];
            var data = keys.includes(key) ? bundle.pk : bundle.values;
            data[key] = value;
            return bundle;
        }, { pk: {}, values: {} }), pk = _b.pk, values = _b.values;
        return db(exports.sqlSchemaResource(schemaResource))
            .where(pk) // pull only keys from query || ensure it's being done upstream
            .update(values, supportsReturn ? context.fields : undefined); // remove keys & cannot update fields from query && fields exists. was set in generic
    };
};
exports.toDeleteQuery = function (keys) {
    return function (_a) {
        var db = _a.db, st = _a.st, resource = _a.resource, query = _a.query, searchQuery = _a.searchQuery, hardDelete = _a.hardDelete, schemaResource = _a.schemaResource;
        var pk = Object.entries(query).reduce(function (bundle, _a) {
            var key = _a[0], value = _a[1];
            var data = keys.includes(key) ? bundle.pk : bundle.values;
            data[key] = value;
            return bundle;
        }, { pk: {}, values: {} }).pk;
        if (hardDelete) {
            return db(exports.sqlSchemaResource(schemaResource)).where(pk)["delete"]();
        }
        // if soft delete [this will need to support custom `active` columns as dbs likely have these flags with different names]
        pk.active = true;
        var sqlcount = db(exports.sqlSchemaResource(schemaResource)).count().where(pk);
        var sqlUpdate = db(exports.sqlSchemaResource(schemaResource))
            .where(pk)
            .update({ active: false });
        var softDelete = new Promise(function (resolve, reject) { return __awaiter(void 0, void 0, void 0, function () {
            var count, n, err_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 4, , 5]);
                        return [4 /*yield*/, sqlcount];
                    case 1:
                        count = (_a.sent())[0].count;
                        n = Number(count);
                        if (!(n !== 0)) return [3 /*break*/, 3];
                        // no need to do the delete if no matching records exist. Call it a day
                        return [4 /*yield*/, sqlUpdate];
                    case 2:
                        // no need to do the delete if no matching records exist. Call it a day
                        _a.sent();
                        _a.label = 3;
                    case 3:
                        resolve(n);
                        return [3 /*break*/, 5];
                    case 4:
                        err_1 = _a.sent();
                        reject(err_1);
                        return [3 /*break*/, 5];
                    case 5: return [2 /*return*/];
                }
            });
        }); });
        softDelete.toString = function () { return sqlUpdate.toString(); };
        return softDelete;
    };
};
exports.aggregationFnBuilder = function (db) {
    return function (calculatedFields, groupBy) {
        return function (knex_query) {
            var defineCalculatedFields = function (arr) {
                return arr.map(function (item) {
                    return calculatedFields[item]
                        ? db.raw(calculatedFields[item] + " as " + item)
                        : item;
                });
            };
            // @ts-ignore // replace field name with calculation def before executing
            knex_query._statements = knex_query._statements.map(function (_a) {
                var grouping = _a.grouping, value = _a.value;
                return ({
                    grouping: grouping,
                    value: grouping !== "columns" ? value : defineCalculatedFields(value)
                });
            });
            return groupBy ? knex_query.groupBy(groupBy) : knex_query;
        };
    };
};
exports.genCountQuery = function (db, knex_query) {
    // explictely remove sql limit
    // https://github.com/knex/knex/blob/e37aeaa31c8ef9c1b07d2e4d3ec6607e557d800d/lib/query/compiler.js#L522
    // https://github.com/knex/knex/blob/master/lib/query/builder.js#L872
    // @ts-ignore -- accessing private property
    knex_query._single.limit = undefined;
    var count_alias = db.client.config.client == "oracledb" ? "" : " as count_query";
    var count_query = db.from(db.raw("(" + knex_query.toString() + ")" + count_alias));
    // this is needed to make the db result mysql/postgres agnostic
    return count_query.count("* as count");
};
//# sourceMappingURL=database.js.map