@@ -8,6 +8,21 @@ const Literal = Object.getPrototypeOf(Sequelize.literal('foo')).constructor
8
8
const sqlOutput = Symbol ( 'sqlOutput' )
9
9
const queryGeneratorSymbol = Symbol ( 'queryGenerator' )
10
10
11
+ class ValuesRow {
12
+ value : string
13
+ constructor ( value : string ) {
14
+ this . value = value
15
+ }
16
+ }
17
+
18
+ function isValuesArray ( expression : any ) : boolean {
19
+ if ( ! Array . isArray ( expression ) ) return false
20
+ for ( let i = 0 ; i < expression . length ; i ++ ) {
21
+ if ( ! ( expression [ i ] instanceof ValuesRow ) ) return false
22
+ }
23
+ return true
24
+ }
25
+
11
26
function sql (
12
27
strings : $ReadOnlyArray < string > ,
13
28
...expressions : $ReadOnlyArray < mixed >
@@ -20,6 +35,8 @@ function sql(
20
35
const expression = expressions [ i ]
21
36
if ( expression instanceof Literal ) {
22
37
parts . push ( expression . val )
38
+ } else if ( isValuesArray ( expression ) ) {
39
+ parts . push ( ( expression : any ) . map ( row => row . value ) . join ( ', ' ) )
23
40
} else if ( expression instanceof Object && expression [ sqlOutput ] ) {
24
41
const [ query , options ] = expression
25
42
parts . push ( query . replace ( / ( \$ + ) ( \d + ) / g, ( match : string , dollars : string , index : string ) =>
@@ -57,45 +74,93 @@ function findQueryGenerator(expressions: $ReadOnlyArray<mixed>): QueryGenerator
57
74
return ( expression : any ) . QueryGenerator
58
75
} else if ( expression && expression . type instanceof Sequelize . ABSTRACT ) {
59
76
return ( expression : any ) . Model . QueryGenerator
77
+ } else if ( expression instanceof Sequelize ) {
78
+ return expression . getQueryInterface ( ) . QueryGenerator
60
79
}
61
80
}
62
- throw new Error ( `at least one of the expressions must be a sequelize Model or attribute` )
81
+ throw new Error ( `at least one of the expressions must be a sequelize Model, attribute, or Sequelize instance` )
82
+ }
83
+
84
+ const once = < F : Function > (fn: F): F => {
85
+ let called = false
86
+ let result : any
87
+ return ( ( ) : any => {
88
+ if ( called ) return result
89
+ called = true
90
+ return result = fn ( )
91
+ } : any )
63
92
}
64
93
65
- sql . escape = function escapeSql (
94
+ const escapeSql = (
95
+ queryGenerator: () => QueryGenerator ,
96
+ ) => (
66
97
strings : $ReadOnlyArray < string > ,
67
98
...expressions: $ReadOnlyArray< mixed >
68
- ) : string {
99
+ ): string = > {
69
100
const parts : Array < string > = [ ]
70
- let queryGenerator : ?QueryGenerator
71
- function getQueryGenerator ( ) : QueryGenerator {
72
- return queryGenerator || ( queryGenerator = findQueryGenerator ( expressions ) )
73
- }
74
-
75
101
for ( let i = 0 , { length} = expressions ; i < length ; i ++ ) {
76
102
parts . push ( strings [ i ] )
77
103
const expression = expressions [ i ]
78
104
if ( expression instanceof Literal ) {
79
105
parts . push ( expression . val )
106
+ } else if (isValuesArray(expression)) {
107
+ parts . push ( ( expression : any ) . map ( row => row . value ) . join ( ', ' ) )
80
108
} else if (expression instanceof Object && expression [ sqlOutput ] ) {
81
109
const [ query , options ] = expression
82
110
parts . push ( query . replace ( / ( \$ + ) ( \d + ) / g, ( match : string , dollars : string , index : string ) =>
83
111
dollars . length % 2 === 0
84
112
? match
85
- : getQueryGenerator ( ) . escape ( options . bind [ parseInt ( index ) - 1 ] )
113
+ : queryGenerator ( ) . escape ( options . bind [ parseInt ( index ) - 1 ] )
86
114
) )
87
115
} else if (expression && expression . prototype instanceof Model ) {
88
116
const { tableName} = ( expression : any )
89
- parts . push ( getQueryGenerator ( ) . quoteTable ( tableName ) )
117
+ parts . push ( queryGenerator ( ) . quoteTable ( tableName ) )
90
118
} else if (expression && expression . type instanceof Sequelize . ABSTRACT ) {
91
119
const { field} = ( expression : any )
92
- parts . push ( getQueryGenerator ( ) . quoteIdentifier ( field ) )
120
+ parts . push ( queryGenerator ( ) . quoteIdentifier ( field ) )
93
121
} else {
94
- parts . push ( getQueryGenerator ( ) . escape ( expression ) )
122
+ parts . push ( queryGenerator ( ) . escape ( expression ) )
95
123
}
96
124
}
97
125
parts . push ( strings [ expressions . length ] )
98
- return parts . join ( '') . trim ( ) . replace ( / \s + / g , ' ' )
126
+ return parts . join ( '') . trim ( )
99
127
}
100
128
129
+ sql . escape = (
130
+ strings : $ReadOnlyArray < string > ,
131
+ ...expressions: $ReadOnlyArray< mixed >
132
+ ): string => escapeSql ( once ( ( ) => findQueryGenerator ( expressions ) ) ) ( strings , ...expressions )
133
+
134
+ sql . literal = (
135
+ strings : $ReadOnlyArray < string > ,
136
+ ...expressions: $ReadOnlyArray< mixed >
137
+ ): Literal => Sequelize . literal ( escapeSql ( once ( ( ) => findQueryGenerator ( expressions ) ) ) ( strings , ...expressions ) )
138
+
139
+ sql . with = (
140
+ sequelize : Sequelize
141
+ ) => ( {
142
+ escape : (
143
+ strings : $ReadOnlyArray < string > ,
144
+ ...expressions : $ReadOnlyArray < mixed >
145
+ ) : string => escapeSql ( ( ) => sequelize . getQueryInterface ( ) . QueryGenerator ) ( strings , ...expressions ) ,
146
+ values : (
147
+ strings : $ReadOnlyArray < string > ,
148
+ ...expressions : $ReadOnlyArray < mixed >
149
+ ) : ValuesRow => new ValuesRow ( escapeSql ( ( ) => sequelize . getQueryInterface ( ) . QueryGenerator ) ( strings , ...expressions ) ) ,
150
+ literal : (
151
+ strings : $ReadOnlyArray < string > ,
152
+ ...expressions : $ReadOnlyArray < mixed >
153
+ ) : Literal => Sequelize . literal ( escapeSql ( ( ) => sequelize . getQueryInterface ( ) . QueryGenerator ) ( strings , ...expressions ) ) ,
154
+ query : (
155
+ strings : $ReadOnlyArray < string > ,
156
+ ...expressions : $ReadOnlyArray < mixed >
157
+ ) => ( options : QueryOptions = { } ) : Promise < any > => {
158
+ const [ query , baseOptions ] = sql ( strings , ...expressions )
159
+ return sequelize . query (
160
+ query ,
161
+ { ...baseOptions , ...options }
162
+ )
163
+ }
164
+ } )
165
+
101
166
module . exports = sql
0 commit comments