phantasy-mysql
Version:
267 lines (221 loc) • 8.04 kB
Flow
// @flow
import type { QueryCriteria, QueryObj, QueryValues } from './sql/types';
import { limit } from './sql/limit';
import { offset } from './sql/offset';
import { where } from './sql/where';
/**
* `Query` class
*/
export class Query<Props: {}> {
props: Props
constructor(props: Props): void {
this.props = props;
}
}
/**
* `SelectQuery` class
*/
export class SelectQuery<Props: {}> extends Query<Props> {
fields(field: string, ...fields: Array<string>): SelectQuery<Props & { fields: Array<string> }> {
return new SelectQuery({ ...this.props, fields: [ field, ...fields ] });
}
from(table: string): SelectQuery<Props & { table: string }> {
return new SelectQuery({ ...this.props, table });
}
where(...criteria: QueryCriteria[]): SelectQuery<Props & { where: QueryCriteria[] }> {
return new SelectQuery({ ...this.props, where: criteria });
}
limit(n: number): SelectQuery<Props & { limit: number }> {
return new SelectQuery({ ...this.props, limit: n });
}
offset(n: number): SelectQuery<Props & { offset: number }> {
return new SelectQuery({ ...this.props, offset: n });
}
}
/**
* `InsertQuery` class
*/
export class InsertQuery<Props: { data: QueryValues[] }> extends Query<Props> {
into(table: string): InsertQuery<Props & { table: string }> {
return new InsertQuery({ ...this.props, table });
}
}
/**
* `ReplaceQuery` class
*/
export class ReplaceQuery<Props: { data: QueryValues[] }> extends Query<Props> {
into(table: string): ReplaceQuery<Props & { table: string }> {
return new ReplaceQuery({ ...this.props, table });
}
}
/**
* `UpdateQuery` class
*/
export class UpdateQuery<Props: {}> extends Query<Props> {
table(table: string) {
return new UpdateQuery({ ...this.props, table });
}
set(data: QueryValues): UpdateQuery<Props & { data: QueryValues }> {
return new UpdateQuery({ ...this.props, data });
}
where(...criteria: QueryCriteria[]): UpdateQuery<Props & { where: QueryCriteria[] }> {
return new UpdateQuery({ ...this.props, where: criteria });
}
limit(n: number): UpdateQuery<Props & { limit: number }> {
return new UpdateQuery({ ...this.props, limit: n });
}
}
/**
* `DeleteQuery` class
*/
export class DeleteQuery<Props: {}> extends Query<Props> {
from(table: string): DeleteQuery<Props & { table: string }> {
return new DeleteQuery({ ...this.props, table });
}
where(...criteria: QueryCriteria[]): DeleteQuery<Props & { where: QueryCriteria[] }> {
return new DeleteQuery({ ...this.props, where: criteria });
}
limit(n: number): DeleteQuery<Props & { limit: number }> {
return new DeleteQuery({ ...this.props, limit: n });
}
offset(n: number): DeleteQuery<Props & { offset: number }> {
return new DeleteQuery({ ...this.props, offset: n });
}
}
export type BuiltQuery =
// SELECT
| SelectQuery<{ table: string, fields: string[] }>
| SelectQuery<{ table: string, fields: string[], where: QueryCriteria[] }>
| SelectQuery<{ table: string, fields: string[], limit: number }>
| SelectQuery<{ table: string, fields: string[], offset: number }>
| SelectQuery<{ table: string, fields: string[], where: QueryCriteria[], limit: number }>
| SelectQuery<{ table: string, fields: string[], where: QueryCriteria[], offset: number }>
| SelectQuery<{ table: string, fields: string[], limit: number, offset: number }>
| SelectQuery<{ table: string, fields: string[], where: QueryCriteria[], limit: number, offset: number }>
// INSERT
| InsertQuery<{ table: string, data: QueryValues[] }>
// REPLACE
| ReplaceQuery<{ table: string, data: QueryValues[] }>
// UPDATE
| UpdateQuery<{ table: string, data: QueryValues }>
| UpdateQuery<{ table: string, data: QueryValues, where: QueryCriteria[] }>
| UpdateQuery<{ table: string, data: QueryValues, limit: number }>
| UpdateQuery<{ table: string, data: QueryValues, where: QueryCriteria[], limit: number }>
// DELETE
| DeleteQuery<{ table: string }>
| DeleteQuery<{ table: string, where: QueryCriteria[] }>
| DeleteQuery<{ table: string, limit: number }>
| DeleteQuery<{ table: string, where: QueryCriteria[], limit: number }>
| DeleteQuery<{ table: string, offset: number }>
| DeleteQuery<{ table: string, where: QueryCriteria[], offset: number }>
| DeleteQuery<{ table: string, limit: number, offset: number }>
| DeleteQuery<{ table: string, where: QueryCriteria[], limit: number, offset: number }>
/**
* `toSql :: BuiltQuery -> QueryObj`
*/
export function toSql(q: BuiltQuery): QueryObj {
if (q instanceof SelectQuery) {
const props = q.props,
{ sql: limitSql, args: limitArgs } = limit(props),
{ sql: offsetSql, args: offsetArgs } = offset(props),
{ sql: whereSql, args: whereArgs } = where(props);
return {
sql: `SELECT ${ props.fields.join(', ') } FROM ${ props.table } ${ [ whereSql, limitSql, offsetSql ].filter(sql => sql.length > 0).join(' ') }`.trim(),
args: [ ...whereArgs, ...limitArgs, ...offsetArgs ]
};
}
else if (q instanceof InsertQuery) {
const props = q.props,
records = props.data,
fields = Object.keys(records[0]);
const recordSql = records.map(record => {
const placeholders = Object.keys(record).map(() => `?`);
return `(${ placeholders.join(', ') })`;
})
.join(', ');
const args = records.map(record => {
return Object.keys(record).map(key => record[key]);
})
.reduce((args, recordArgs) => [ ...args, ...recordArgs ], []);
return {
sql: `INSERT INTO ${ props.table } (${ fields.join(', ') }) VALUES ${ recordSql }`.trim(),
args
};
}
else if (q instanceof ReplaceQuery) {
const props = q.props,
records = props.data,
fields = Object.keys(records[0]);
const recordSql = records.map(record => {
const placeholders = Object.keys(record).map(() => `?`);
return `(${ placeholders.join(', ') })`;
})
.join(', ');
const args = records.map(record => {
return Object.keys(record).map(key => record[key]);
})
.reduce((args, recordArgs) => [ ...args, ...recordArgs ], []);
return {
sql: `REPLACE INTO ${ props.table } (${ fields.join(', ') }) VALUES ${ recordSql }`.trim(),
args
};
}
else if (q instanceof UpdateQuery) {
const props = q.props,
keys = Object.keys(props.data),
setSql = keys.map(key => `${ key } = ?`).join(', '),
setArgs = keys.map(key => props.data[key]),
{ sql: limitSql, args: limitArgs } = limit(props),
{ sql: whereSql, args: whereArgs } = where(props);
return {
sql: `UPDATE ${ props.table } SET ${ setSql } ${ [ whereSql, limitSql ].filter(sql => sql.length > 0).join(' ') }`.trim(),
args: [ ...setArgs, ...whereArgs, ...limitArgs ]
};
}
else if (q instanceof DeleteQuery) {
const props = q.props,
{ sql: limitSql, args: limitArgs } = limit(props),
{ sql: offsetSql, args: offsetArgs } = offset(props),
{ sql: whereSql, args: whereArgs } = where(props);
return {
sql: `DELETE FROM ${ props.table } ${ [ whereSql, limitSql, offsetSql ].filter(sql => sql.length > 0).join(' ') }`.trim(),
args: [ ...whereArgs, ...limitArgs, ...offsetArgs ]
};
}
else {
/* istanbul ignore next */
(q: empty); // eslint-disable-line no-unused-expressions
/* istanbul ignore next */
throw new TypeError();
}
}
/**
* `Select :: string[] -> SelectQuery`
*/
export function Select(): SelectQuery<{}> {
return new SelectQuery({});
}
/**
* `Insert :: QueryValues[] -> InsertQuery`
*/
export function Insert(data: QueryValues, ...datas: QueryValues[]): InsertQuery<{ data: QueryValues[] }> {
return new InsertQuery({ data: [ data, ...datas ] });
}
/**
* `Replace :: QueryValues[] -> ReplaceQuery`
*/
export function Replace(data: QueryValues, ...datas: QueryValues[]): ReplaceQuery<{ data: QueryValues[] }> {
return new ReplaceQuery({ data: [ data, ...datas ] });
}
/**
* `Update :: string -> UpdateQuery`
*/
export function Update(): UpdateQuery<{}> {
return new UpdateQuery({});
}
/**
* `Delete :: () -> DeleteQuery`
*/
export function Delete(..._: void[]): DeleteQuery<{}> { // eslint-disable-line no-unused-vars
return new DeleteQuery({});
}