You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
6.0 KiB

import {WhereOperator} from '../../../lib/src/collection/Where.ts'
import RawValue from './RawValue.ts'
import {Select} from './type/Select.ts'
/**
* Represents a field or set of fields.
*/
export type FieldSet = string | string[]
/**
* Represents a table name, or table name and alias.
*/
export type QuerySource = string | { ref: QuerySource, alias: string }
/**
* Valid JOIN clause operators.
*/
export type JoinOperator = 'JOIN' | 'LEFT JOIN' | 'LEFT OUTER JOIN' | 'RIGHT JOIN' | 'RIGHT OUTER JOIN' | 'FULL OUTER JOIN' | 'INNER JOIN' | 'CROSS JOIN'
/**
* Valid operators which can join WHERE clauses.
*/
export type WherePreOperator = 'AND' | 'OR' | 'AND NOT' | 'OR NOT'
/**
* Abstract representation of a single WHERE clause.
*/
export type WhereClause = { field: string, operator: SQLWhereOperator, operand: string, preop: WherePreOperator }
/**
* Group of where clauses, and the operator which should join them.
*/
export type WhereGroup = { items: WhereStatement[], preop: WherePreOperator }
/**
* A single WHERE statement.
*/
export type WhereStatement = WhereClause | WhereGroup
/**
* Operators which can be used in SQL WHERE clauses.
*/
export type SQLWhereOperator = WhereOperator | 'IN' | 'NOT IN' | 'LIKE' | 'BETWEEN' | 'NOT BETWEEN' | 'IS' | 'IS NOT'
/**
* Directions for ORDER BY clauses.
*/
export type OrderDirection = 'ASC' | 'DESC'
/**
* Abstract representation of an ORDER BY clause.
*/
export type OrderStatement = { direction: OrderDirection, field: string }
/**
* Valid operators which can join HAVING clauses.
*/
export type HavingPreOperator = WherePreOperator
/**
* Abstract representation of a single HAVING clause.
*/
export type HavingClause = WhereClause
/**
* Group of having clauses, and the operator which should join them.
*/
export type HavingGroup = WhereGroup
/**
* A single HAVING statement.
*/
export type HavingStatement = HavingClause | HavingGroup
/**
* Valid operators which can be used in SQL HAVING clauses.
*/
export type SQLHavingOperator = SQLWhereOperator
/**
* A value which can be escaped to be interpolated into an SQL query.
*/
export type EscapedValue = string | number | boolean | Date | RawValue | EscapedValue[] | Select<any>
/**
* Representation of a field and its value.
*/
export type FieldValue = { field: string, value: EscapedValue }
/**
* Object representation of a number of fields and their values.
*/
export type FieldValueObject = { [field: string]: EscapedValue }
/**
* Abstract reference to a particular database table, and its alias.
*/
export type TableRef = {
table: string,
database?: string,
alias?: string
}
/**
* Returns true if the given object is a valid table ref.
* @param something
* @return boolean
*/
export function isTableRef(something: any): something is TableRef {
let is = true
is = is && typeof something?.table === 'string'
if ( typeof something?.database !== 'undefined' ) {
is = typeof something?.database === 'string'
}
if ( typeof something?.alias !== 'undefined' ) {
is = typeof something?.alias === 'string'
}
return is
}
/**
* Returns true if the given item is a valid WHERE pre-operator.
* @param something
* @return boolean
*/
export function isWherePreOperator(something: any): something is WherePreOperator {
return ['AND', 'OR', 'AND NOT', 'OR NOT'].includes(something)
}
/**
* Returns true if the given item is a valid HAVING clause.
* @param something
* @return boolean
*/
export function isHavingClause(something: any): something is HavingClause {
return isWhereClause(something)
}
/**
* Returns true if the given item is a valid WHERE clause.
* @param something
* @return boolean
*/
export function isWhereClause(something: any): something is WhereClause {
return typeof something?.field === 'string'
&& typeof something?.operator === 'string' // TODO check this better
&& typeof something?.operand === 'string'
&& isWherePreOperator(something?.preop)
}
/**
* Returns true if the given item is a valid HAVING group.
* @param something
* @return boolean
*/
export function isHavingGroup(something: any): something is HavingGroup {
return isWhereGroup(something)
}
/**
* Returns true if the given item is a valid WHERE group.
* @param something
* @return boolean
*/
export function isWhereGroup(something: any): something is WhereGroup {
return Array.isArray(something?.items)
&& something.items.every((item: any) => isWhereStatement(item))
&& isWherePreOperator(something?.preop)
}
/**
* Returns true if the given value is a valid where statement.
* @param something
* @return boolean
*/
export function isWhereStatement(something: any): something is WhereStatement {
return isWhereClause(something) || isWhereGroup(something)
}
/**
* Escapes the value so it can be inserted into an SQL query string.
* @param {EscapedValue} value
* @return string
*/
export function escape(value: EscapedValue): string {
if ( value instanceof Select ) {
return `(${value.sql(5)})`
} else if ( value instanceof RawValue ) {
return value.value
} else if ( Array.isArray(value) ) {
return `(${value.map(escape).join(',')})`
} else if ( String(value).toLowerCase() === 'true' ) {
return 'TRUE'
} else if ( String(value).toLowerCase() === 'false' ) {
return 'FALSE'
} else if ( typeof value === 'number' ) {
return `${value}`
} else if ( value === true ) {
return 'TRUE'
} else if ( value === false ) {
return 'FALSE'
} else if ( value instanceof Date ) { // TODO custom formattig
const pad = (val: number) => val < 10 ? `0${val}` : `${val}`
return `'${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())} ${pad(value.getHours())}:${pad(value.getMinutes())}:${pad(value.getSeconds())}'`
} else if ( !isNaN(Number(value)) ) {
return String(Number(value))
} else {
const escaped = value.replace(/"/g, '\\"').replace(/'/g, '\\\'').replace(/`/g, '\\`')
return `'${escaped}'`
}
}