"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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
class Postgres {
    constructor(knex) {
        this.knex = knex;
        this.schema = 'public';
    }
    // Postgres specific
    // ===============================================================================================
    /**
     * Set the schema to be used in other methods
     */
    withSchema(schema) {
        this.schema = schema;
        return this;
    }
    /**
     * Converts Postgres default value to JS
     * Eg `'example'::character varying` => `example`
     */
    parseDefaultValue(type) {
        if (type.startsWith('nextval('))
            return null; // auto-increment
        const parts = type.split('::');
        let value = parts[0];
        if (value.startsWith("'") && value.endsWith("'")) {
            value = value.slice(1, -1);
        }
        if (parts[1] && parts[1].includes('json'))
            return JSON.parse(value);
        if (parts[1] && (parts[1].includes('char') || parts[1].includes('text')))
            return String(value);
        if (Number.isNaN(Number(value)))
            return value;
        return Number(value);
    }
    // Tables
    // ===============================================================================================
    /**
     * List all existing tables in the current schema/database
     */
    tables() {
        return __awaiter(this, void 0, void 0, function* () {
            const records = yield this.knex
                .select('tablename')
                .from('pg_catalog.pg_tables')
                .where({ schemaname: this.schema });
            return records.map(({ tablename }) => tablename);
        });
    }
    tableInfo(table) {
        return __awaiter(this, void 0, void 0, function* () {
            const query = this.knex
                .select('table_name', 'table_schema', this.knex
                .select(this.knex.raw('obj_description(oid)'))
                .from('pg_class')
                .where({ relkind: 'r' })
                .andWhere({ relname: 'table_name ' })
                .as('table_comment'))
                .from('information_schema.tables')
                .where({ table_schema: this.schema })
                .andWhere({ table_catalog: this.knex.client.database() })
                .andWhere({ table_type: 'BASE TABLE' })
                .orderBy('table_name', 'asc');
            if (table) {
                const rawTable = yield query
                    .andWhere({ table_name: table })
                    .limit(1)
                    .first();
                return {
                    name: rawTable.table_name,
                    schema: rawTable.table_schema,
                    comment: rawTable.table_comment,
                };
            }
            const records = yield query;
            return records.map((rawTable) => {
                return {
                    name: rawTable.table_name,
                    schema: rawTable.table_schema,
                    comment: rawTable.table_comment,
                };
            });
        });
    }
    /**
     * Check if a table exists in the current schema/database
     */
    hasTable(table) {
        return __awaiter(this, void 0, void 0, function* () {
            const subquery = this.knex
                .select()
                .from('information_schema.tables')
                .where({ table_name: table, table_schema: this.schema });
            const record = yield this.knex
                .select(this.knex.raw('exists (?)', [subquery]))
                .first();
            return (record === null || record === void 0 ? void 0 : record.exists) || false;
        });
    }
    // Columns
    // ===============================================================================================
    /**
     * Get all the available columns in the current schema/database. Can be filtered to a specific table
     */
    columns(table) {
        return __awaiter(this, void 0, void 0, function* () {
            const query = this.knex
                .select('table_name', 'column_name')
                .from('information_schema.columns')
                .where({ table_schema: this.schema });
            if (table) {
                query.andWhere({ table_name: table });
            }
            const records = yield query;
            return records.map(({ table_name, column_name }) => ({
                table: table_name,
                column: column_name,
            }));
        });
    }
    columnInfo(table, column) {
        return __awaiter(this, void 0, void 0, function* () {
            const { knex } = this;
            const query = knex
                .select('c.column_name', 'c.table_name', 'c.data_type', 'c.column_default', 'c.character_maximum_length', 'c.is_nullable', knex
                .select(knex.raw(`'YES'`))
                .from('pg_index')
                .join('pg_attribute', function () {
                this.on('pg_attribute.attrelid', '=', 'pg_index.indrelid').andOn(knex.raw('pg_attribute.attnum = any(pg_index.indkey)'));
            })
                .whereRaw('pg_index.indrelid = c.table_name::regclass')
                .andWhere(knex.raw('pg_attribute.attname = c.column_name'))
                .andWhere(knex.raw('pg_index.indisprimary'))
                .as('is_primary'), knex
                .select(knex.raw('pg_catalog.col_description(pg_catalog.pg_class.oid, c.ordinal_position:: int)'))
                .from('pg_catalog.pg_class')
                .whereRaw(`pg_catalog.pg_class.oid = (select('"' || c.table_name || '"'):: regclass:: oid)`)
                .andWhere({ 'pg_catalog.pg_class.relname': 'c.table_name' })
                .as('column_comment'), knex.raw('pg_get_serial_sequence(c.table_name, c.column_name) as serial'), 'ffk.referenced_table_schema', 'ffk.referenced_table_name', 'ffk.referenced_column_name')
                .from(knex.raw('information_schema.columns c'))
                .joinRaw(`
        LEFT JOIN (
          SELECT 
            k1.table_schema, 
            k1.table_name, 
            k1.column_name, 
            k2.table_schema AS referenced_table_schema, 
            k2.table_name AS referenced_table_name, 
            k2.column_name AS referenced_column_name 
          FROM 
            information_schema.key_column_usage k1 
            JOIN information_schema.referential_constraints fk using (
              constraint_schema, constraint_name
            ) 
            JOIN information_schema.key_column_usage k2 ON k2.constraint_schema = fk.unique_constraint_schema 
            AND k2.constraint_name = fk.unique_constraint_name 
            AND k2.ordinal_position = k1.position_in_unique_constraint
        ) ffk ON ffk.table_name = c.table_name 
        AND ffk.column_name = c.column_name 
      `)
                .where({ 'c.table_schema': this.schema });
            if (table) {
                query.andWhere({ 'c.table_name': table });
            }
            if (column) {
                const rawColumn = yield query
                    .andWhere({ 'c.column_name': column })
                    .first();
                return {
                    name: rawColumn.column_name,
                    table: rawColumn.table_name,
                    type: rawColumn.data_type,
                    default_value: rawColumn.column_default
                        ? this.parseDefaultValue(rawColumn.column_default)
                        : null,
                    max_length: rawColumn.character_maximum_length,
                    is_nullable: rawColumn.is_nullable === 'YES',
                    is_primary_key: rawColumn.is_primary === 'YES',
                    has_auto_increment: rawColumn.serial !== null,
                    foreign_key_column: rawColumn.referenced_column_name,
                    foreign_key_table: rawColumn.referenced_table_name,
                    comment: rawColumn.column_comment,
                    schema: this.schema,
                    foreign_key_schema: rawColumn.referenced_table_schema,
                };
            }
            const records = yield query;
            return records.map((rawColumn) => {
                return {
                    name: rawColumn.column_name,
                    table: rawColumn.table_name,
                    type: rawColumn.data_type,
                    default_value: rawColumn.column_default
                        ? this.parseDefaultValue(rawColumn.column_default)
                        : null,
                    max_length: rawColumn.character_maximum_length,
                    is_nullable: rawColumn.is_nullable === 'YES',
                    is_primary_key: rawColumn.is_primary === 'YES',
                    has_auto_increment: rawColumn.serial !== null,
                    foreign_key_column: rawColumn.referenced_column_name,
                    foreign_key_table: rawColumn.referenced_table_name,
                    comment: rawColumn.column_comment,
                    schema: this.schema,
                    foreign_key_schema: rawColumn.referenced_table_schema,
                };
            });
        });
    }
    /**
     * Check if the given table contains the given column
     */
    hasColumn(table, column) {
        return __awaiter(this, void 0, void 0, function* () {
            const subquery = this.knex
                .select()
                .from('information_schema.columns')
                .where({
                table_schema: this.schema,
                table_name: table,
                column_name: column,
            });
            const record = yield this.knex
                .select(this.knex.raw('exists (?)', [subquery]))
                .first();
            return (record === null || record === void 0 ? void 0 : record.exists) || false;
        });
    }
    /**
     * Get the primary key column for the given table
     */
    primary(table) {
        return __awaiter(this, void 0, void 0, function* () {
            const { column_name } = yield this.knex
                .select('information_schema.key_column_usage.column_name')
                .from('information_schema.key_column_usage')
                .leftJoin('information_schema.table_constraints', 'information_schema.table_constraints.constraint_name', 'information_schema.key_column_usage.constraint_name')
                .where({
                'information_schema.table_constraints.constraint_type': 'PRIMARY KEY',
                'information_schema.table_constraints.table_name': table,
                'information_schema.table_constraints.table_schema': this.schema,
            })
                .first();
            return column_name;
        });
    }
}
exports.default = Postgres;
