Skip to content

Commit

Permalink
feat: make sql template tagged literal a stand-alone function
Browse files Browse the repository at this point in the history
BREAKING CHANGE: connection.query and other methods are no longer allowed to be called as template literals. Use “sql” import to construct the query input.
  • Loading branch information
gajus committed Jun 5, 2017
1 parent 17b8c54 commit fb27293
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 61 deletions.
8 changes: 6 additions & 2 deletions README.md
Expand Up @@ -162,10 +162,14 @@ SELECT $1

### Tagged template literals

Query methods can be executed using [tagged template literals](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals) syntax, e.g.
Query methods can be executed using `sql` [tagged template literal](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals), e.g.

```js
connection.query`INSERT INTO reservation_ticket (reservation_id, ticket_id) VALUES ${values}`;
import {
sql
} from 'mightyql'

connection.query(sql`INSERT INTO reservation_ticket (reservation_id, ticket_id) VALUES ${values}`);

```

Expand Down
40 changes: 25 additions & 15 deletions src/index.js
Expand Up @@ -20,6 +20,7 @@ import {
stripComments
} from './utilities';
import type {
AnonymouseValuePlaceholderValuesType,
ClientConfigurationType,
DatabaseConfigurationType,
DatabasePoolType,
Expand All @@ -29,7 +30,8 @@ import type {
InternalQueryMaybeOneType,
InternalQueryOneType,
InternalQueryType,
QueryResultRowType
QueryResultRowType,
TaggledTemplateLiteralInvocationType
} from './types';

export type {
Expand Down Expand Up @@ -64,9 +66,9 @@ export const firstColumn = (rows: $ReadOnlyArray<QueryResultRowType>) => {
const debug = createDebug('mightyql');

export const query: InternalQueryType<*> = async (connection, rawSql, values) => {
const sql = stripComments(rawSql);
const strippedSql = stripComments(rawSql);

debug('input query', sql, {
debug('input query', strippedSql, {
values
});

Expand All @@ -79,18 +81,18 @@ export const query: InternalQueryType<*> = async (connection, rawSql, values) =>
const {
sql: normalizedSql,
values: normalizedValues
} = normalizeAnonymousValuePlaceholders(sql, values);
} = normalizeAnonymousValuePlaceholders(strippedSql, values);

result = await connection.query(normalizedSql, normalizedValues);
} else if (values) {
const {
sql: normalizedSql,
values: normalizedValues
} = normalizeNamedValuePlaceholders(sql, values);
} = normalizeNamedValuePlaceholders(strippedSql, values);

result = await connection.query(normalizedSql, normalizedValues);
} else {
result = await connection.query(sql);
result = await connection.query(strippedSql);
}

const end = process.hrtime(start);
Expand All @@ -117,10 +119,10 @@ export const query: InternalQueryType<*> = async (connection, rawSql, values) =>
* @throws NotFoundError If query returns no rows.
* @throws DataIntegrityError If query returns multiple rows.
*/
export const one: InternalQueryOneType = async (connection, clientConfiguration, sql, values) => {
export const one: InternalQueryOneType = async (connection, clientConfiguration, raqSql, values) => {
const {
rows
} = await query(connection, sql, values);
} = await query(connection, raqSql, values);

if (rows.length === 0) {
const ConfigurableNotFoundError = clientConfiguration.errors && clientConfiguration.errors.NotFoundError ? clientConfiguration.errors.NotFoundError : NotFoundError;
Expand All @@ -140,10 +142,10 @@ export const one: InternalQueryOneType = async (connection, clientConfiguration,
*
* @throws DataIntegrityError If query returns multiple rows.
*/
export const maybeOne: InternalQueryMaybeOneType = async (connection, clientConfiguration, sql, values) => {
export const maybeOne: InternalQueryMaybeOneType = async (connection, clientConfiguration, raqSql, values) => {
const {
rows
} = await query(connection, sql, values);
} = await query(connection, raqSql, values);

if (rows.length === 0) {
return null;
Expand All @@ -161,10 +163,10 @@ export const maybeOne: InternalQueryMaybeOneType = async (connection, clientConf
*
* @throws NotFoundError If query returns no rows.
*/
export const many: InternalQueryManyType = async (connection, clientConfiguration, sql, values) => {
export const many: InternalQueryManyType = async (connection, clientConfiguration, raqSql, values) => {
const {
rows
} = await query(connection, sql, values);
} = await query(connection, raqSql, values);

if (rows.length === 0) {
const ConfigurableNotFoundError = clientConfiguration.errors && clientConfiguration.errors.NotFoundError ? clientConfiguration.errors.NotFoundError : NotFoundError;
Expand All @@ -178,14 +180,21 @@ export const many: InternalQueryManyType = async (connection, clientConfiguratio
/**
* Makes a query and expects any number of results.
*/
export const any: InternalQueryAnyType = async (connection, clientConfiguration, sql, values) => {
export const any: InternalQueryAnyType = async (connection, clientConfiguration, raqSql, values) => {
const {
rows
} = await query(connection, sql, values);
} = await query(connection, raqSql, values);

return rows;
};

const sql = (parts: $ReadOnlyArray<string>, ...values: AnonymouseValuePlaceholderValuesType): TaggledTemplateLiteralInvocationType => {
return {
sql: parts.join('?'),
values
};
};

const createConnection = async (
connectionConfiguration: DatabaseConfigurationType,
clientConfiguration: ClientConfigurationType = {}
Expand Down Expand Up @@ -245,5 +254,6 @@ const createPool = (

export {
createConnection,
createPool
createPool,
sql
};
19 changes: 12 additions & 7 deletions src/types.js
Expand Up @@ -76,14 +76,14 @@ export type NormalizedQueryType = {|

type QueryPrimitiveValueType = string | number | null;

// eslint-disable-next-line flowtype/generic-spacing
export type AnonymouseValuePlaceholderValuesType = $ReadOnlyArray<
export type AnonymouseValuePlaceholderValueType =

// INSERT ... VALUES ? => INSERT ... VALUES (1, 2, 3); [[1, 2, 3]]
// INSERT ... VALUES ? => INSERT ... VALUES (1), (2), (3); [[[1], [2], [3]]]
$ReadOnlyArray<QueryPrimitiveValueType | $ReadOnlyArray<QueryPrimitiveValueType>> |
QueryPrimitiveValueType
>;
// INSERT ... VALUES ? => INSERT ... VALUES (1, 2, 3); [[1, 2, 3]]
// INSERT ... VALUES ? => INSERT ... VALUES (1), (2), (3); [[[1], [2], [3]]]
$ReadOnlyArray<QueryPrimitiveValueType | $ReadOnlyArray<QueryPrimitiveValueType>> |
QueryPrimitiveValueType;

export type AnonymouseValuePlaceholderValuesType = $ReadOnlyArray<AnonymouseValuePlaceholderValueType>;

export type NamedValuePlaceholderValuesType = {
+[key: string]: string | number | null
Expand All @@ -93,6 +93,11 @@ export type DatabaseQueryValuesType =
AnonymouseValuePlaceholderValuesType |
NamedValuePlaceholderValuesType;

export type TaggledTemplateLiteralInvocationType = {
sql: string,
values: $ReadOnlyArray<AnonymouseValuePlaceholderValueType>
};

export type InternalQueryAnyType = (
connection: InternalDatabaseConnectionType,
clientConfiguration: ClientConfigurationType,
Expand Down
17 changes: 9 additions & 8 deletions src/utilities/mapTaggedTemplateLiteralInvocation.js
@@ -1,15 +1,16 @@
// @flow

// eslint-disable-next-line flowtype/no-weak-types
export default (targetMethod: Function) => {
// eslint-disable-next-line flowtype/no-weak-types
return (maybeQuery: string | Array<string>, ...args: any) => {
import type {
DatabaseQueryValuesType,
TaggledTemplateLiteralInvocationType
} from '../types';

export default (targetMethod: *) => {
return (maybeQuery: string | TaggledTemplateLiteralInvocationType, values?: DatabaseQueryValuesType) => {
if (typeof maybeQuery === 'string') {
return targetMethod(maybeQuery, args[0]);
return targetMethod(maybeQuery, values);
} else {
const strings = maybeQuery;

return targetMethod(strings.join('?'), args);
return targetMethod(maybeQuery.sql, maybeQuery.values);
}
};
};
29 changes: 0 additions & 29 deletions test/utilities/mapTaggedTemplateLiteralInvocation.js

This file was deleted.

0 comments on commit fb27293

Please sign in to comment.