import {escape, EscapedValue, FieldValue, FieldValueObject, QuerySource, WhereStatement, FieldSet} from '../types.ts' import {Collection} from '../../../../lib/src/collection/Collection.ts' import {WhereBuilder} from './WhereBuilder.ts' import {applyMixins} from '../../../../lib/src/support/mixins.ts' import {TableRefBuilder} from './TableRefBuilder.ts' import {MalformedSQLGrammarError} from './Select.ts' import ConnectionMutable from './ConnectionMutable.ts' import {Scope} from '../Scope.ts' // TODO WHERE CURRENT OF /** * Query builder base class for UPDATE queries. * @extends ConnectionMutable * @extends TableRefBuilder * @extends WhereBuilder */ export class Update extends ConnectionMutable { /** * The target table to be updated. * @type QuerySource */ protected _target?: QuerySource = undefined /** * Include the ONLY clause? * @type boolean */ protected _only = false /** * Field value sets to be updated. * @type Collection */ protected _sets: Collection = new Collection() /** * Where clauses to be applied. * @type Array */ protected _wheres: WhereStatement[] = [] /** * Scopes to be applied. * @type Array */ protected _scopes: Scope[] = [] /** * Fields to update. * @type Array */ protected _fields: string[] = [] 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.') const table_ref = this.source_alias_to_table_ref(this._target) const wheres = this.wheres_to_sql(this._wheres, level + 1) const returning_fields = this._fields.join(', ') return [ `UPDATE ${this._only ? 'ONLY ' : ''}${this.serialize_table_ref(table_ref)}`, `SET`, this.serialize_sets(this._sets, level + 1), ...(wheres.trim() ? ['WHERE', wheres] : []), ...(returning_fields.trim() ? [`RETURNING ${returning_fields}`] : []), ].filter(x => String(x).trim()).join(`\n${indent}`) } /** * Helper to serialize field value sets to raw SQL. * @param {Collection} sets * @param {number} level - the indentation level * @return string */ protected serialize_sets(sets: Collection, level = 0): string { const indent = Array(level * 2).fill(' ').join('') return indent + sets.map(field_value => `${field_value.field} = ${escape(field_value.value)}`).join(`,\n${indent}`) } /** * Target table to update records in. * @param {QuerySource} source * @param {string} [alias] * @return self */ to(source: QuerySource, alias?: string) { if ( !alias ) this._target = source else this._target = { ref: source, alias } return this } /** * Add the ONLY clause. * @return self */ only() { this._only = true return this } /** * Add a field and value to the update clause. * @param {string} field * @param {EscapedValue} value * @return self */ set(field: string, value: EscapedValue) { const existing = this._sets.firstWhere('field', '=', field) if ( existing ) { existing.value = value } else { this._sets.push({ field, value }) } return this } /** * Add a set of fields and values to the update clause. * @param {FieldValueObject} values * @return self */ data(values: FieldValueObject) { for ( const field in values ) { if ( !values.hasOwnProperty(field) ) continue this.set(field, values[field]) } return this } /** * Set the fields to be returned after the update. * @param {...FieldSet} fields * @return self */ 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) } } } return this } } export interface Update extends WhereBuilder, TableRefBuilder {} applyMixins(Update, [WhereBuilder, TableRefBuilder])