Skip to content
Toggle navigation
Projects
Groups
Snippets
Help
public
/
sequelize
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
不要怂,就是干,撸起袖子干!
Commit 722ed505
authored
Apr 20, 2020
by
Andy Edwards
Committed by
GitHub
Apr 20, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor(dialects/postgres): asyncify methods (#12129)
1 parent
ceb0de26
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
163 additions
and
170 deletions
lib/dialects/postgres/connection-manager.js
lib/dialects/postgres/query-interface.js
lib/dialects/postgres/query.js
lib/dialects/postgres/connection-manager.js
View file @
722ed50
...
@@ -84,7 +84,7 @@ class ConnectionManager extends AbstractConnectionManager {
...
@@ -84,7 +84,7 @@ class ConnectionManager extends AbstractConnectionManager {
return
this
.
lib
.
types
.
getTypeParser
(
oid
,
...
args
);
return
this
.
lib
.
types
.
getTypeParser
(
oid
,
...
args
);
}
}
connect
(
config
)
{
async
connect
(
config
)
{
config
.
user
=
config
.
username
;
config
.
user
=
config
.
username
;
const
connectionConfig
=
_
.
pick
(
config
,
[
const
connectionConfig
=
_
.
pick
(
config
,
[
'user'
,
'password'
,
'host'
,
'database'
,
'port'
'user'
,
'password'
,
'host'
,
'database'
,
'port'
...
@@ -121,7 +121,7 @@ class ConnectionManager extends AbstractConnectionManager {
...
@@ -121,7 +121,7 @@ class ConnectionManager extends AbstractConnectionManager {
]));
]));
}
}
return
new
Promise
((
resolve
,
reject
)
=>
{
const
connection
=
await
new
Promise
((
resolve
,
reject
)
=>
{
let
responded
=
false
;
let
responded
=
false
;
const
connection
=
new
this
.
lib
.
Client
(
connectionConfig
);
const
connection
=
new
this
.
lib
.
Client
(
connectionConfig
);
...
@@ -194,76 +194,71 @@ class ConnectionManager extends AbstractConnectionManager {
...
@@ -194,76 +194,71 @@ class ConnectionManager extends AbstractConnectionManager {
resolve
(
connection
);
resolve
(
connection
);
}
}
});
});
}).
then
(
connection
=>
{
});
let
query
=
''
;
if
(
this
.
sequelize
.
options
.
standardConformingStrings
!==
false
&&
connection
[
'standard_conforming_strings'
]
!==
'on'
)
{
// Disable escape characters in strings
// see https://github.com/sequelize/sequelize/issues/3545 (security issue)
// see https://www.postgresql.org/docs/current/static/runtime-config-compatible.html#GUC-STANDARD-CONFORMING-STRINGS
query
+=
'SET standard_conforming_strings=on;'
;
}
if
(
this
.
sequelize
.
options
.
clientMinMessages
!==
false
)
{
let
query
=
''
;
query
+=
`SET client_min_messages TO
${
this
.
sequelize
.
options
.
clientMinMessages
}
;`
;
}
if
(
!
this
.
sequelize
.
config
.
keepDefaultTimezone
)
{
if
(
this
.
sequelize
.
options
.
standardConformingStrings
!==
false
&&
connection
[
'standard_conforming_strings'
]
!==
'on'
)
{
const
isZone
=
!!
moment
.
tz
.
zone
(
this
.
sequelize
.
options
.
timezone
);
// Disable escape characters in strings
if
(
isZone
)
{
// see https://github.com/sequelize/sequelize/issues/3545 (security issue)
query
+=
`SET TIME ZONE '
${
this
.
sequelize
.
options
.
timezone
}
';`
;
// see https://www.postgresql.org/docs/current/static/runtime-config-compatible.html#GUC-STANDARD-CONFORMING-STRINGS
}
else
{
query
+=
'SET standard_conforming_strings=on;'
;
query
+=
`SET TIME ZONE INTERVAL '
${
this
.
sequelize
.
options
.
timezone
}
' HOUR TO MINUTE;`
;
}
}
}
if
(
query
)
{
if
(
this
.
sequelize
.
options
.
clientMinMessages
!==
false
)
{
return
Promise
.
resolve
(
connection
.
query
(
query
)).
then
(()
=>
connection
);
query
+=
`SET client_min_messages TO
${
this
.
sequelize
.
options
.
clientMinMessages
}
;`
;
}
}
return
connection
;
}).
then
(
connection
=>
{
if
(
!
this
.
sequelize
.
config
.
keepDefaultTimezone
)
{
if
(
Object
.
keys
(
this
.
nameOidMap
).
length
===
0
&&
const
isZone
=
!!
moment
.
tz
.
zone
(
this
.
sequelize
.
options
.
timezone
);
this
.
enumOids
.
oids
.
length
===
0
&&
if
(
isZone
)
{
this
.
enumOids
.
arrayOids
.
length
===
0
)
{
query
+=
`SET TIME ZONE '
${
this
.
sequelize
.
options
.
timezone
}
';`
;
return
Promise
.
resolve
(
this
.
_refreshDynamicOIDs
(
connection
)).
then
(()
=>
connection
);
}
else
{
query
+=
`SET TIME ZONE INTERVAL '
${
this
.
sequelize
.
options
.
timezone
}
' HOUR TO MINUTE;`
;
}
}
return
connection
;
}
}).
then
(
connection
=>
{
// Don't let a Postgres restart (or error) to take down the whole app
if
(
query
)
{
connection
.
on
(
'error'
,
error
=>
{
await
connection
.
query
(
query
);
connection
.
_invalid
=
true
;
}
debug
(
`connection error
${
error
.
code
||
error
.
message
}
`
);
if
(
Object
.
keys
(
this
.
nameOidMap
).
length
===
0
&&
this
.
pool
.
destroy
(
connection
);
this
.
enumOids
.
oids
.
length
===
0
&&
});
this
.
enumOids
.
arrayOids
.
length
===
0
)
{
return
connection
;
await
this
.
_refreshDynamicOIDs
(
connection
);
}
// Don't let a Postgres restart (or error) to take down the whole app
connection
.
on
(
'error'
,
error
=>
{
connection
.
_invalid
=
true
;
debug
(
`connection error
${
error
.
code
||
error
.
message
}
`
);
this
.
pool
.
destroy
(
connection
);
});
});
return
connection
;
}
}
disconnect
(
connection
)
{
async
disconnect
(
connection
)
{
if
(
connection
.
_ending
)
{
if
(
connection
.
_ending
)
{
debug
(
'connection tried to disconnect but was already at ENDING state'
);
debug
(
'connection tried to disconnect but was already at ENDING state'
);
return
Promise
.
resolve
()
;
return
;
}
}
return
promisify
(
callback
=>
connection
.
end
(
callback
))();
return
await
promisify
(
callback
=>
connection
.
end
(
callback
))();
}
}
validate
(
connection
)
{
validate
(
connection
)
{
return
!
connection
.
_invalid
&&
!
connection
.
_ending
;
return
!
connection
.
_invalid
&&
!
connection
.
_ending
;
}
}
_refreshDynamicOIDs
(
connection
)
{
async
_refreshDynamicOIDs
(
connection
)
{
const
databaseVersion
=
this
.
sequelize
.
options
.
databaseVersion
;
const
databaseVersion
=
this
.
sequelize
.
options
.
databaseVersion
;
const
supportedVersion
=
'8.3.0'
;
const
supportedVersion
=
'8.3.0'
;
// Check for supported version
// Check for supported version
if
(
(
databaseVersion
&&
semver
.
gte
(
databaseVersion
,
supportedVersion
))
===
false
)
{
if
(
(
databaseVersion
&&
semver
.
gte
(
databaseVersion
,
supportedVersion
))
===
false
)
{
return
Promise
.
resolve
()
;
return
;
}
}
// Refresh dynamic OIDs for some types
const
results
=
await
(
connection
||
this
.
sequelize
).
query
(
// These include Geometry / Geography / HStore / Enum / Citext / Range
return
(
connection
||
this
.
sequelize
).
query
(
'WITH ranges AS ('
+
'WITH ranges AS ('
+
' SELECT pg_range.rngtypid, pg_type.typname AS rngtypname,'
+
' SELECT pg_range.rngtypid, pg_type.typname AS rngtypname,'
+
' pg_type.typarray AS rngtyparray, pg_range.rngsubtype'
+
' pg_type.typarray AS rngtyparray, pg_range.rngsubtype'
+
...
@@ -273,46 +268,46 @@ class ConnectionManager extends AbstractConnectionManager {
...
@@ -273,46 +268,46 @@ class ConnectionManager extends AbstractConnectionManager {
' ranges.rngtypname, ranges.rngtypid, ranges.rngtyparray'
+
' ranges.rngtypname, ranges.rngtypid, ranges.rngtyparray'
+
' FROM pg_type LEFT OUTER JOIN ranges ON pg_type.oid = ranges.rngsubtype'
+
' FROM pg_type LEFT OUTER JOIN ranges ON pg_type.oid = ranges.rngsubtype'
+
' WHERE (pg_type.typtype IN(\'b\', \'e\'));'
' WHERE (pg_type.typtype IN(\'b\', \'e\'));'
)
.
then
(
results
=>
{
)
;
let
result
=
Array
.
isArray
(
results
)
?
results
.
pop
()
:
results
;
let
result
=
Array
.
isArray
(
results
)
?
results
.
pop
()
:
results
;
// When searchPath is prepended then two statements are executed and the result is
// an array of those two statements. First one is the SET search_path and second
is
// When searchPath is prepended then two statements are executed and the result
is
// the SELECT query result.
// an array of those two statements. First one is the SET search_path and second is
if
(
Array
.
isArray
(
result
))
{
// the SELECT query result.
if
(
result
[
0
].
command
===
'SET'
)
{
if
(
Array
.
isArray
(
result
)
)
{
result
=
result
.
pop
();
if
(
result
[
0
].
command
===
'SET'
)
{
}
result
=
result
.
pop
();
}
}
}
const
newNameOidMap
=
{};
const
newNameOidMap
=
{};
const
newEnumOids
=
{
oids
:
[],
arrayOids
:
[]
};
const
newEnumOids
=
{
oids
:
[],
arrayOids
:
[]
};
for
(
const
row
of
result
.
rows
)
{
for
(
const
row
of
result
.
rows
)
{
// Mapping enums, handled separatedly
// Mapping enums, handled separatedly
if
(
row
.
typtype
===
'e'
)
{
if
(
row
.
typtype
===
'e'
)
{
newEnumOids
.
oids
.
push
(
row
.
oid
);
newEnumOids
.
oids
.
push
(
row
.
oid
);
if
(
row
.
typarray
)
newEnumOids
.
arrayOids
.
push
(
row
.
typarray
);
if
(
row
.
typarray
)
newEnumOids
.
arrayOids
.
push
(
row
.
typarray
);
continue
;
continue
;
}
}
// Mapping base types and their arrays
// Mapping base types and their arrays
newNameOidMap
[
row
.
typname
]
=
{
oid
:
row
.
oid
};
newNameOidMap
[
row
.
typname
]
=
{
oid
:
row
.
oid
};
if
(
row
.
typarray
)
newNameOidMap
[
row
.
typname
].
arrayOid
=
row
.
typarray
;
if
(
row
.
typarray
)
newNameOidMap
[
row
.
typname
].
arrayOid
=
row
.
typarray
;
// Mapping ranges(of base types) and their arrays
// Mapping ranges(of base types) and their arrays
if
(
row
.
rngtypid
)
{
if
(
row
.
rngtypid
)
{
newNameOidMap
[
row
.
typname
].
rangeOid
=
row
.
rngtypid
;
newNameOidMap
[
row
.
typname
].
rangeOid
=
row
.
rngtypid
;
if
(
row
.
rngtyparray
)
newNameOidMap
[
row
.
typname
].
arrayRangeOid
=
row
.
rngtyparray
;
if
(
row
.
rngtyparray
)
newNameOidMap
[
row
.
typname
].
arrayRangeOid
=
row
.
rngtyparray
;
}
}
}
}
// Replace all OID mappings. Avoids temporary empty OID mappings.
// Replace all OID mappings. Avoids temporary empty OID mappings.
this
.
nameOidMap
=
newNameOidMap
;
this
.
nameOidMap
=
newNameOidMap
;
this
.
enumOids
=
newEnumOids
;
this
.
enumOids
=
newEnumOids
;
this
.
refreshTypeParser
(
dataTypes
.
postgres
);
this
.
refreshTypeParser
(
dataTypes
.
postgres
);
});
}
}
_clearDynamicOIDs
()
{
_clearDynamicOIDs
()
{
...
...
lib/dialects/postgres/query-interface.js
View file @
722ed50
...
@@ -26,7 +26,7 @@ const _ = require('lodash');
...
@@ -26,7 +26,7 @@ const _ = require('lodash');
* @returns {Promise}
* @returns {Promise}
* @private
* @private
*/
*/
function
ensureEnums
(
qi
,
tableName
,
attributes
,
options
,
model
)
{
async
function
ensureEnums
(
qi
,
tableName
,
attributes
,
options
,
model
)
{
const
keys
=
Object
.
keys
(
attributes
);
const
keys
=
Object
.
keys
(
attributes
);
const
keyLen
=
keys
.
length
;
const
keyLen
=
keys
.
length
;
...
@@ -50,108 +50,106 @@ function ensureEnums(qi, tableName, attributes, options, model) {
...
@@ -50,108 +50,106 @@ function ensureEnums(qi, tableName, attributes, options, model) {
}
}
}
}
return
Promise
.
all
(
promises
).
then
(
results
=>
{
const
results
=
await
Promise
.
all
(
promises
);
promises
=
[];
promises
=
[];
let
enumIdx
=
0
;
let
enumIdx
=
0
;
// This little function allows us to re-use the same code that prepends or appends new value to enum array
// This little function allows us to re-use the same code that prepends or appends new value to enum array
const
addEnumValue
=
(
field
,
value
,
relativeValue
,
position
=
'before'
,
spliceStart
=
promises
.
length
)
=>
{
const
addEnumValue
=
(
field
,
value
,
relativeValue
,
position
=
'before'
,
spliceStart
=
promises
.
length
)
=>
{
const
valueOptions
=
_
.
clone
(
options
);
const
valueOptions
=
_
.
clone
(
options
);
valueOptions
.
before
=
null
;
valueOptions
.
before
=
null
;
valueOptions
.
after
=
null
;
valueOptions
.
after
=
null
;
switch
(
position
)
{
switch
(
position
)
{
case
'after'
:
case
'after'
:
valueOptions
.
after
=
relativeValue
;
valueOptions
.
after
=
relativeValue
;
break
;
break
;
case
'before'
:
case
'before'
:
default
:
default
:
valueOptions
.
before
=
relativeValue
;
valueOptions
.
before
=
relativeValue
;
break
;
break
;
}
}
promises
.
splice
(
spliceStart
,
0
,
()
=>
{
promises
.
splice
(
spliceStart
,
0
,
()
=>
{
return
qi
.
sequelize
.
query
(
qi
.
QueryGenerator
.
pgEnumAdd
(
return
qi
.
sequelize
.
query
(
qi
.
QueryGenerator
.
pgEnumAdd
(
tableName
,
field
,
value
,
valueOptions
tableName
,
field
,
value
,
valueOptions
),
valueOptions
);
),
valueOptions
);
});
});
};
};
for
(
i
=
0
;
i
<
keyLen
;
i
++
)
{
const
attribute
=
attributes
[
keys
[
i
]];
const
type
=
attribute
.
type
;
const
enumType
=
type
.
type
||
type
;
const
field
=
attribute
.
field
||
keys
[
i
];
if
(
type
instanceof
DataTypes
.
ENUM
||
type
instanceof
DataTypes
.
ARRAY
&&
enumType
instanceof
DataTypes
.
ENUM
//ARRAY sub type is ENUM
)
{
// If the enum type doesn't exist then create it
if
(
!
results
[
enumIdx
])
{
promises
.
push
(()
=>
{
return
qi
.
sequelize
.
query
(
qi
.
QueryGenerator
.
pgEnum
(
tableName
,
field
,
enumType
,
options
),
Object
.
assign
({},
options
,
{
raw
:
true
}));
});
}
else
if
(
!!
results
[
enumIdx
]
&&
!!
model
)
{
const
enumVals
=
qi
.
QueryGenerator
.
fromArray
(
results
[
enumIdx
].
enum_value
);
const
vals
=
enumType
.
values
;
// Going through already existing values allows us to make queries that depend on those values
// We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values
// Then we append the rest of new values AFTER the latest already existing value
// E.g.: [1,2] -> [0,2,1] ==> [1,0,2]
// E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4]
// E.g.: [1] -> [0,2,3] ==> [1,0,2,3]
let
lastOldEnumValue
;
let
rightestPosition
=
-
1
;
for
(
let
oldIndex
=
0
;
oldIndex
<
enumVals
.
length
;
oldIndex
++
)
{
const
enumVal
=
enumVals
[
oldIndex
];
const
newIdx
=
vals
.
indexOf
(
enumVal
);
lastOldEnumValue
=
enumVal
;
if
(
newIdx
===
-
1
)
{
continue
;
}
const
newValuesBefore
=
vals
.
slice
(
0
,
newIdx
);
for
(
i
=
0
;
i
<
keyLen
;
i
++
)
{
const
promisesLength
=
promises
.
length
;
const
attribute
=
attributes
[
keys
[
i
]];
// we go in reverse order so we could stop when we meet old value
const
type
=
attribute
.
type
;
for
(
let
reverseIdx
=
newValuesBefore
.
length
-
1
;
reverseIdx
>=
0
;
reverseIdx
--
)
{
const
enumType
=
type
.
type
||
type
;
if
(
~
enumVals
.
indexOf
(
newValuesBefore
[
reverseIdx
]))
{
const
field
=
attribute
.
field
||
keys
[
i
];
break
;
}
addEnumValue
(
field
,
newValuesBefore
[
reverseIdx
],
lastOldEnumValue
,
'before'
,
promisesLength
);
if
(
}
type
instanceof
DataTypes
.
ENUM
||
type
instanceof
DataTypes
.
ARRAY
&&
enumType
instanceof
DataTypes
.
ENUM
//ARRAY sub type is ENUM
)
{
// If the enum type doesn't exist then create it
if
(
!
results
[
enumIdx
])
{
promises
.
push
(()
=>
{
return
qi
.
sequelize
.
query
(
qi
.
QueryGenerator
.
pgEnum
(
tableName
,
field
,
enumType
,
options
),
Object
.
assign
({},
options
,
{
raw
:
true
}));
});
}
else
if
(
!!
results
[
enumIdx
]
&&
!!
model
)
{
const
enumVals
=
qi
.
QueryGenerator
.
fromArray
(
results
[
enumIdx
].
enum_value
);
const
vals
=
enumType
.
values
;
// Going through already existing values allows us to make queries that depend on those values
// We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values
// Then we append the rest of new values AFTER the latest already existing value
// E.g.: [1,2] -> [0,2,1] ==> [1,0,2]
// E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4]
// E.g.: [1] -> [0,2,3] ==> [1,0,2,3]
let
lastOldEnumValue
;
let
rightestPosition
=
-
1
;
for
(
let
oldIndex
=
0
;
oldIndex
<
enumVals
.
length
;
oldIndex
++
)
{
const
enumVal
=
enumVals
[
oldIndex
];
const
newIdx
=
vals
.
indexOf
(
enumVal
);
lastOldEnumValue
=
enumVal
;
if
(
newIdx
===
-
1
)
{
continue
;
}
// we detect the most 'right' position of old value in new enum array so we can append new values to it
const
newValuesBefore
=
vals
.
slice
(
0
,
newIdx
);
if
(
newIdx
>
rightestPosition
)
{
const
promisesLength
=
promises
.
length
;
rightestPosition
=
newIdx
;
// we go in reverse order so we could stop when we meet old value
for
(
let
reverseIdx
=
newValuesBefore
.
length
-
1
;
reverseIdx
>=
0
;
reverseIdx
--
)
{
if
(
~
enumVals
.
indexOf
(
newValuesBefore
[
reverseIdx
]))
{
break
;
}
}
addEnumValue
(
field
,
newValuesBefore
[
reverseIdx
],
lastOldEnumValue
,
'before'
,
promisesLength
);
}
}
if
(
lastOldEnumValue
&&
rightestPosition
<
vals
.
length
-
1
)
{
// we detect the most 'right' position of old value in new enum array so we can append new values to it
const
remainingEnumValues
=
vals
.
slice
(
rightestPosition
+
1
);
if
(
newIdx
>
rightestPosition
)
{
for
(
let
reverseIdx
=
remainingEnumValues
.
length
-
1
;
reverseIdx
>=
0
;
reverseIdx
--
)
{
rightestPosition
=
newIdx
;
addEnumValue
(
field
,
remainingEnumValues
[
reverseIdx
],
lastOldEnumValue
,
'after'
);
}
}
}
}
enumIdx
++
;
if
(
lastOldEnumValue
&&
rightestPosition
<
vals
.
length
-
1
)
{
const
remainingEnumValues
=
vals
.
slice
(
rightestPosition
+
1
);
for
(
let
reverseIdx
=
remainingEnumValues
.
length
-
1
;
reverseIdx
>=
0
;
reverseIdx
--
)
{
addEnumValue
(
field
,
remainingEnumValues
[
reverseIdx
],
lastOldEnumValue
,
'after'
);
}
}
}
enumIdx
++
;
}
}
}
}
}
return
promises
const
result
=
await
promises
.
reduce
((
promise
,
asyncFunction
)
=>
promise
.
then
(
asyncFunction
),
Promise
.
resolve
())
.
reduce
(
async
(
promise
,
asyncFunction
)
=>
await
asyncFunction
(
await
promise
),
Promise
.
resolve
());
.
then
(
result
=>
{
// If ENUM processed, then refresh OIDs
// If ENUM processed, then refresh OIDs
if
(
promises
.
length
)
{
if
(
promises
.
length
)
{
return
Promise
.
resolve
(
qi
.
sequelize
.
dialect
.
connectionManager
.
_refreshDynamicOIDs
()).
then
(()
=>
result
);
await
qi
.
sequelize
.
dialect
.
connectionManager
.
_refreshDynamicOIDs
();
}
}
return
result
;
return
result
;
});
});
}
}
...
...
lib/dialects/postgres/query.js
View file @
722ed50
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment