import ConnectionMutable from './ConnectionMutable.ts' import {EscapedValue, FieldValueObject, QuerySource} from '../types.ts' import {MalformedSQLGrammarError} from './Select.ts' import {TableRefBuilder} from './TableRefBuilder.ts' import {applyMixins} from '../../../../lib/src/support/mixins.ts' import {escape, FieldSet} from '../types.ts' import {raw} from '../Builder.ts' // TODO support DEFAULT VALUES // TODO support ON CONFLICT /** * Query builder base for INSERT queries. * @extends ConnectionMutable * @extends TableRefBuilder */ export class Insert extends ConnectionMutable { /** * The target table to insert into. * @type QuerySource */ protected _target?: QuerySource = undefined /** * The columns to insert. * @type Array */ protected _columns: string[] = [] /** * The row data to insert. * @type Array */ protected _rows: string[] = [] /** * The fields to insert. * @type Array */ protected _fields: string[] = [] /** * Return all data? * @type boolean */ protected _return_all = false sql(level = 0): string { const indent = Array(level * 2).fill(' ').join('') if ( typeof this._target === 'undefined' ) throw new MalformedSQLGrammarError('No table reference has been provided.') if ( this._rows.length < 1 ) throw new MalformedSQLGrammarError('There are no rows to insert.') const table_ref = this.source_alias_to_table_ref(this._target) const returning = this._return_all ? this._columns.join(', ') : this._fields.join(', ') const fields = escape(this._columns.map(x => raw(x))) return [ `INSERT INTO ${this.serialize_table_ref(table_ref)}`, ` ${fields}`, 'VALUES', ` ${this._rows.join(',\n ')}`, ...(returning.trim() ? [`RETURNING ${returning}`] : []) ].filter(x => String(x).trim()).join(`\n${indent}`) } /** * Set the table to insert into. * @param {QuerySource} source * @param {string} [alias] * @return Insert */ into(source: QuerySource, alias?: string) { if ( !alias ) this._target = source else this._target = { ref: source, alias } return this } /** * Set the columns to insert. * @param {...string} columns * @return Insert */ columns(...columns: string[]) { this._columns = columns return this } /** * Add raw row data to insert. * @param {...EscapedValue} row * @return Insert */ row_raw(...row: EscapedValue[]) { if ( row.length !== this._columns.length ) throw new MalformedSQLGrammarError(`Cannot insert row with ${row.length} values using a query that has ${this._columns.length} columns specified.`) this._rows.push(escape(row)) return this } /** * Add a field value object to insert. * @param {FieldValueObject} row * @return Insert */ row(row: FieldValueObject) { const columns = [] const row_raw = [] for ( const field in row ) { if ( !row.hasOwnProperty(field) ) continue columns.push(field) row_raw.push(row[field]) } this.columns(...columns) this.row_raw(...row_raw) return this } /** * Add multiple field value objects to insert. * @param {Array}rows * @return Insert */ rows(rows: FieldValueObject[]) { const [initial, ...rest] = rows const columns = [] const initial_raw = [] for ( const field in initial ) { if ( !initial.hasOwnProperty(field) ) continue columns.push(field) initial_raw.push(initial[field]) } this.columns(...columns) this.row_raw(...initial_raw) for ( const row of rest ) { const values = [] for ( const col of columns ) { values.push(row[col]) } this.row_raw(...values) } return this } /** * Set the fields to return after insert. * @param {...FieldSet} fields * @return Insert */ returning(...fields: FieldSet[]) { for ( const field_set of fields ) { if ( typeof field_set === 'string' ) { if ( !this._fields.includes(field_set) ) this._fields.push(field_set) } else { for ( const field of field_set ) { if ( !this._fields.includes(field) ) this._fields.push(field) } } } this._return_all = this._fields.length === 0 return this } } export interface Insert extends TableRefBuilder {} applyMixins(Insert, [TableRefBuilder])