<commit_before>import * as uuid from "uuid"

import { Client } from "./client"
import { Collate, Engine } from "./constant"

export interface Options {
    primary: string[]
    collate?: Collate
    engine?: Engine
}

export declare class Model {
    static readonly TABLE_NAME: string
    static setClient(client: Client): typeof Model
    static ensureTable(options?: Options): void
    static ensureIndex(columns: string[]): void
    static ensureUniqueIndex(columns: string[]): void
    static ensureFullTextIndex(columns: string[]): void
    static get(query: object): Model | null
    static list(query: object): Model[]
    static delete(query: object): number
    static insert(row: object, options?: { upsert: boolean }): number
    static upsert(row: object): number
    static batchInsert(rows: object[], options?: { upsert: boolean }): number[]
    static batchUpsert(rows: object[]): number[]
    static struct(json: object): Model
}

export type TableModel = typeof Model

export function Table(name: string): TableModel {
    class __Model__ {
        public static readonly TABLE_NAME = name
        private static columns = {}
        private static client: Client

        constructor() {
            Object.keys(__Model__.columns).forEach(key => {
                this[key] = __Model__.columns[key].init()
            })
        }

        protected setColumn(key: string, type: string, init: any) {
            __Model__.columns[key] = { key, type, init }
        }

        static setClient(client: Client) {
            this.client = client
            return this
        }

        static ensureTable(options: Options) {
            this.client.execute(`
                CREATE TABLE IF NOT EXISTS ${this.TABLE_NAME} (
                    ${options.primary.map(key =>
                        key + " " + this.columns[key].type + ","
                    )}
                    PRIMARY KEY (${options.primary.join(",")})
                )
                ${options && options.collate ? " COLLATE " + options.collate : ""}
                ${options && options.engine ? " ENGINE " + options.engine: ""}
            `)
            Object.keys(this.columns).forEach(key => {
                this.client.ensureColumn(
                    this.TABLE_NAME,
                    key,
                    this.columns[key].type
                )
            })
        }

        static ensureIndex(columns: string[]) {
            this.client.ensureIndex(this.TABLE_NAME, columns)
        }

        static ensureUniqueIndex(columns: string[]) {
            this.client.ensureUniqueIndex(this.TABLE_NAME, columns)
        }

        static ensureFullTextIndex(columns: string[]) {
            this.client.ensureFullTextIndex(this.TABLE_NAME, columns)
        }

        static get(query: object) {
            let { sql, args } = this.queryToSQL(`SELECT * FROM ${this.TABLE_NAME} WHERE TRUE`, query)
            let row = this.client.queryForObject(sql, args)
            return row ? this.struct(row) : null
        }

        static list(query: object) {
            let { sql, args } = this.queryToSQL(`SELECT * FROM ${this.TABLE_NAME} WHERE TRUE`, query)
            return this.client.query(sql, args).map(row => this.struct(row))
        }

        static delete(query: object) {
            let { sql, args } = this.queryToSQL(`DELETE FROM ${this.TABLE_NAME} WHERE TRUE`, query)
            if (args.length === 0) {
                throw "Cannot delete rows with an empty query"
            } else {
                return this.client.update(sql, args)
            }
        }

        static insert(row: object, options = { upsert: false }) {
            let model = this.struct(row)
            let keys = Object.keys(this.columns)
            return this.client.update(
                `${options.upsert ? "REPLACE" : "INSERT"} INTO
                    ${this.TABLE_NAME} (${keys.join(",")})
                    VALUES (${keys.map(() => "?").join(",")})
                `,
                keys.map(key => {
                    let value = model[key]
                    if (typeof value === "object") {
                        return value === null ? null : JSON.stringify(value)
                    } else {
                        return value
                    }
                })
            )
        }

        static upsert(row: object) {
            return this.insert(row, { upsert: true })
        }

        static batchInsert(rows: object[], options = { upsert: false }) {
            if (rows.length === 0) return
            let that = this
            let keys = Object.keys(this.columns)
            const BatchPreparedStatementSetter = Java.extend(
                Java.type("org.springframework.jdbc.core.BatchPreparedStatementSetter"),
                {
                    setValues(preparedStatement, i) {
                        const model = that.struct(rows[i])
                        keys.forEach((key, j) => {
                            let value = model[key]
                            if (typeof value === "object") {
                                preparedStatement.setObject(j + 1, value === null ? null : JSON.stringify(value))
                            } else {
                                preparedStatement.setObject(j + 1, value)
                            }
                        })
                    },
                    getBatchSize() {
                        return rows.length
                    },
                }
            )
            return this.client.db.batchUpdate(
                `${options.upsert ? "REPLACE" : "INSERT"} INTO
                    ${this.TABLE_NAME} (${keys.join(",")})
                    VALUES (${keys.map(() => "?").join(",")})
                `,
                new BatchPreparedStatementSetter()
            )
        }

        static batchUpsert(rows: object[]) {
            return this.batchInsert(rows, { upsert: true })
        }

        static struct(json: object) {
            let model = new __Model__()
            Object.keys(__Model__.columns).forEach(key => {
                if (json && json[key] !== undefined) model[key] = json[key]
            })
            return model
        }

        private static queryToSQL(sql: string, query: object) {
            let args: any[] = []
            Object.keys(query).forEach(key => {
                sql += ` AND ${key} = ?`
                args.push(query[key])
            })
            return { sql, args }
        }
    }
    return __Model__ as any
}

export const Column = {
    BIGINT(model, key) {
        model.setColumn(key, "BIGINT", () => 0)
    },
    BOOL(model, key) {
        model.setColumn(key, "BOOL", () => false)
    },
    DOUBLE(model, key) {
        model.setColumn(key, "DOUBLE", () => 0)
    },
    JSON(model, key) {
        model.setColumn(key, "JSON", () => null)
    },
    VARCHAR(n: number) {
        return function (model, key) {
            model.setColumn(key, `VARCHAR(${n})`, () => "")
        }
    },
    UUID(model, key) {
        model.setColumn(key, `VARCHAR(40)`, () => uuid.v4())
    },<commit_msg>Add Column.PRIMARY<commit_after>import * as uuid from "uuid"

import { Client } from "./client"
import { Collate, Engine } from "./constant"

export interface Options {
    collate?: Collate
    engine?: Engine
}

export declare class Model {
    static readonly TABLE_NAME: string
    static setClient(client: Client): typeof Model
    static ensureTable(options?: Options): void
    static ensureIndex(columns: string[]): void
    static ensureUniqueIndex(columns: string[]): void
    static ensureFullTextIndex(columns: string[]): void
    static get(query: object): Model | null
    static list(query: object): Model[]
    static delete(query: object): number
    static insert(row: object, options?: { upsert: boolean }): number
    static upsert(row: object): number
    static batchInsert(rows: object[], options?: { upsert: boolean }): number[]
    static batchUpsert(rows: object[]): number[]
    static struct(json: object): Model
}

export type TableModel = typeof Model

export function Table(name: string): TableModel {
    class __Model__ {
        public static readonly TABLE_NAME = name
        private static columns = {}
        private static primary: string[] = []
        private static client: Client

        constructor() {
            Object.keys(__Model__.columns).forEach(key => {
                this[key] = __Model__.columns[key].init()
            })
        }

        protected setColumn(key: string, type: string, init: any) {
            __Model__.columns[key] = { key, type, init }
        }

        protected addPrimary(key: string) {
            __Model__.primary.push(key)
        }

        static setClient(client: Client) {
            this.client = client
            return this
        }

        static ensureTable(options?: Options) {
            this.client.execute(`
                CREATE TABLE IF NOT EXISTS ${this.TABLE_NAME} (
                    ${this.primary.map(key =>
                        key + " " + this.columns[key].type + ","
                    )}
                    PRIMARY KEY (${this.primary.join(",")})
                )
                ${options && options.collate ? " COLLATE " + options.collate : ""}
                ${options && options.engine ? " ENGINE " + options.engine: ""}
            `)
            Object.keys(this.columns).forEach(key => {
                this.client.ensureColumn(
                    this.TABLE_NAME,
                    key,
                    this.columns[key].type
                )
            })
        }

        static ensureIndex(columns: string[]) {
            this.client.ensureIndex(this.TABLE_NAME, columns)
        }

        static ensureUniqueIndex(columns: string[]) {
            this.client.ensureUniqueIndex(this.TABLE_NAME, columns)
        }

        static ensureFullTextIndex(columns: string[]) {
            this.client.ensureFullTextIndex(this.TABLE_NAME, columns)
        }

        static get(query: object) {
            let { sql, args } = this.queryToSQL(`SELECT * FROM ${this.TABLE_NAME} WHERE TRUE`, query)
            let row = this.client.queryForObject(sql, args)
            return row ? this.struct(row) : null
        }

        static list(query: object) {
            let { sql, args } = this.queryToSQL(`SELECT * FROM ${this.TABLE_NAME} WHERE TRUE`, query)
            return this.client.query(sql, args).map(row => this.struct(row))
        }

        static delete(query: object) {
            let { sql, args } = this.queryToSQL(`DELETE FROM ${this.TABLE_NAME} WHERE TRUE`, query)
            if (args.length === 0) {
                throw "Cannot delete rows with an empty query"
            } else {
                return this.client.update(sql, args)
            }
        }

        static insert(row: object, options = { upsert: false }) {
            let model = this.struct(row)
            let keys = Object.keys(this.columns)
            return this.client.update(
                `${options.upsert ? "REPLACE" : "INSERT"} INTO
                    ${this.TABLE_NAME} (${keys.join(",")})
                    VALUES (${keys.map(() => "?").join(",")})
                `,
                keys.map(key => {
                    let value = model[key]
                    if (typeof value === "object") {
                        return value === null ? null : JSON.stringify(value)
                    } else {
                        return value
                    }
                })
            )
        }

        static upsert(row: object) {
            return this.insert(row, { upsert: true })
        }

        static batchInsert(rows: object[], options = { upsert: false }) {
            if (rows.length === 0) return
            let that = this
            let keys = Object.keys(this.columns)
            const BatchPreparedStatementSetter = Java.extend(
                Java.type("org.springframework.jdbc.core.BatchPreparedStatementSetter"),
                {
                    setValues(preparedStatement, i) {
                        const model = that.struct(rows[i])
                        keys.forEach((key, j) => {
                            let value = model[key]
                            if (typeof value === "object") {
                                preparedStatement.setObject(j + 1, value === null ? null : JSON.stringify(value))
                            } else {
                                preparedStatement.setObject(j + 1, value)
                            }
                        })
                    },
                    getBatchSize() {
                        return rows.length
                    },
                }
            )
            return this.client.db.batchUpdate(
                `${options.upsert ? "REPLACE" : "INSERT"} INTO
                    ${this.TABLE_NAME} (${keys.join(",")})
                    VALUES (${keys.map(() => "?").join(",")})
                `,
                new BatchPreparedStatementSetter()
            )
        }

        static batchUpsert(rows: object[]) {
            return this.batchInsert(rows, { upsert: true })
        }

        static struct(json: object) {
            let model = new __Model__()
            Object.keys(__Model__.columns).forEach(key => {
                if (json && json[key] !== undefined) model[key] = json[key]
            })
            return model
        }

        private static queryToSQL(sql: string, query: object) {
            let args: any[] = []
            Object.keys(query).forEach(key => {
                sql += ` AND ${key} = ?`
                args.push(query[key])
            })
            return { sql, args }
        }
    }
    return __Model__ as any
}

export const Column = {
    PRIMARY(model, key) {
        model.addPrimary(key)
    },
    BIGINT(model, key) {
        model.setColumn(key, "BIGINT", () => 0)
    },
    BOOL(model, key) {
        model.setColumn(key, "BOOL", () => false)
    },
    DOUBLE(model, key) {
        model.setColumn(key, "DOUBLE", () => 0)
    },
    JSON(model, key) {
        model.setColumn(key, "JSON", () => null)
    },
    VARCHAR(n: number) {
        return function (model, key) {
            model.setColumn(key, `VARCHAR(${n})`, () => "")
        }
    },
    UUID(model, key) {
        model.setColumn(key, `VARCHAR(40)`, () => uuid.v4())
    },
{'fraction_non_alphanumeric': 0.12501731541764788, 'fraction_numerical': 0.001246710070646904, 'mean_word_length': 1.536718200983837, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4589273', 'n_tokens_mistral': 3871, 'n_tokens_neox': 3707, 'n_words': 872}