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 3d2df28e
authored
Jun 30, 2020
by
Carson McManus
Committed by
GitHub
Jun 30, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(sqlite): describeTable now returns unique and references (#12440)
1 parent
56d07c6d
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
249 additions
and
8 deletions
lib/dialects/sqlite/query-generator.js
lib/dialects/sqlite/query-interface.js
lib/query-interface.js
test/integration/model.test.js
test/integration/model/sync.test.js
test/integration/query-interface/changeColumn.test.js
test/unit/dialects/sqlite/query-generator.test.js
lib/dialects/sqlite/query-generator.js
View file @
3d2df28
...
...
@@ -434,7 +434,8 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator {
).
join
(
', '
);
const
attributeNamesExport
=
Object
.
keys
(
attributes
).
map
(
attr
=>
this
.
quoteIdentifier
(
attr
)).
join
(
', '
);
return
`
${
this
.
createTableQuery
(
backupTableName
,
attributes
).
replace
(
'CREATE TABLE'
,
'CREATE TEMPORARY TABLE'
)
// Temporary tables don't support foreign keys, so creating a temporary table will not allow foreign keys to be preserved
return
`
${
this
.
createTableQuery
(
backupTableName
,
attributes
)
}
INSERT
INTO
$
{
quotedBackupTableName
}
SELECT
${
attributeNamesImport
}
FROM
${
quotedTableName
}
;`
+
`DROP TABLE
${
quotedTableName
}
;
${
this
.
createTableQuery
(
tableName
,
attributes
)
...
...
lib/dialects/sqlite/query-interface.js
View file @
3d2df28
...
...
@@ -60,7 +60,7 @@ function changeColumn(qi, tableName, attributes, options) {
options
=
options
||
{};
return
qi
.
describeTable
(
tableName
,
options
).
then
(
fields
=>
{
fields
[
attributeName
]
=
attributes
[
attributeName
]
;
Object
.
assign
(
fields
[
attributeName
],
attributes
[
attributeName
])
;
const
sql
=
qi
.
QueryGenerator
.
removeColumnQuery
(
tableName
,
fields
);
const
subQueries
=
sql
.
split
(
';'
).
filter
(
q
=>
q
!==
''
);
...
...
@@ -206,3 +206,94 @@ function getForeignKeyReferencesForTable(qi, tableName, options) {
}
exports
.
getForeignKeyReferencesForTable
=
getForeignKeyReferencesForTable
;
/**
* Describe a table structure
*
* This method returns an array of hashes containing information about all attributes in the table.
*
* ```js
* {
* name: {
* type: 'VARCHAR(255)', // this will be 'CHARACTER VARYING' for pg!
* allowNull: true,
* defaultValue: null,
* unique: true, // available for sqlite only
* references: {}, // available for sqlite only
* },
* isBetaMember: {
* type: 'TINYINT(1)', // this will be 'BOOLEAN' for pg!
* allowNull: false,
* defaultValue: false,
* unique: false, // available for sqlite only
* references: {}, // available for sqlite only
* }
* }
* ```
*
* @param {QueryInterface} qi
* @param {string} tableName table name
* @param {Object} [options] Query options
*
* @returns {Promise<Object>}
*/
function
describeTable
(
qi
,
tableName
,
options
)
{
let
schema
=
null
;
let
schemaDelimiter
=
null
;
if
(
typeof
options
===
'string'
)
{
schema
=
options
;
}
else
if
(
typeof
options
===
'object'
&&
options
!==
null
)
{
schema
=
options
.
schema
||
null
;
schemaDelimiter
=
options
.
schemaDelimiter
||
null
;
}
if
(
typeof
tableName
===
'object'
&&
tableName
!==
null
)
{
schema
=
tableName
.
schema
;
tableName
=
tableName
.
tableName
;
}
const
sql
=
qi
.
QueryGenerator
.
describeTableQuery
(
tableName
,
schema
,
schemaDelimiter
);
options
=
Object
.
assign
({},
options
,
{
type
:
QueryTypes
.
DESCRIBE
});
return
qi
.
sequelize
.
query
(
sql
,
options
).
then
(
data
=>
{
/*
* If no data is returned from the query, then the table name may be wrong.
* Query generators that use information_schema for retrieving table info will just return an empty result set,
* it will not throw an error like built-ins do (e.g. DESCRIBE on MySql).
*/
if
(
_
.
isEmpty
(
data
))
{
throw
new
Error
(
`No description found for "
${
tableName
}
" table. Check the table name and schema; remember, they _are_ case sensitive.`
);
}
return
qi
.
showIndex
(
tableName
,
options
).
then
(
indexes
=>
{
for
(
const
prop
in
data
)
{
data
[
prop
].
unique
=
false
;
}
for
(
const
index
of
indexes
)
{
for
(
const
field
of
index
.
fields
)
{
if
(
index
.
unique
!==
undefined
)
{
data
[
field
.
attribute
].
unique
=
index
.
unique
;
}
}
}
return
qi
.
getForeignKeyReferencesForTable
(
tableName
,
options
).
then
(
foreignKeys
=>
{
for
(
const
foreignKey
of
foreignKeys
)
{
data
[
foreignKey
.
columnName
].
references
=
{
model
:
foreignKey
.
referencedTableName
,
key
:
foreignKey
.
referencedColumnName
};
}
return
data
;
});
});
}).
catch
(
e
=>
{
if
(
e
.
original
&&
e
.
original
.
code
===
'ER_NO_SUCH_TABLE'
)
{
throw
Error
(
`No description found for "
${
tableName
}
" table. Check the table name and schema; remember, they _are_ case sensitive.`
);
}
throw
e
;
});
}
exports
.
describeTable
=
describeTable
;
lib/query-interface.js
View file @
3d2df28
...
...
@@ -450,6 +450,11 @@ class QueryInterface {
* @returns {Promise<Object>}
*/
describeTable
(
tableName
,
options
)
{
if
(
this
.
sequelize
.
options
.
dialect
===
'sqlite'
)
{
// sqlite needs some special treatment as it cannot rename a column
return
SQLiteQueryInterface
.
describeTable
(
this
,
tableName
,
options
);
}
let
schema
=
null
;
let
schemaDelimiter
=
null
;
...
...
test/integration/model.test.js
View file @
3d2df28
...
...
@@ -2376,7 +2376,11 @@ describe(Support.getTestDialectTeaser('Model'), () => {
return
UserPublic
.
schema
(
'special'
).
sync
({
force
:
true
}).
then
(()
=>
{
return
this
.
sequelize
.
queryInterface
.
describeTable
(
'Publics'
,
{
logging
(
sql
)
{
if
(
dialect
===
'sqlite'
||
dialect
===
'mysql'
||
dialect
===
'mssql'
||
dialect
===
'mariadb'
)
{
if
(
dialect
===
'sqlite'
&&
sql
.
includes
(
'TABLE_INFO'
))
{
count
++
;
expect
(
sql
).
to
.
not
.
contain
(
'special'
);
}
else
if
([
'mysql'
,
'mssql'
,
'mariadb'
].
includes
(
dialect
))
{
expect
(
sql
).
to
.
not
.
contain
(
'special'
);
count
++
;
}
...
...
@@ -2389,7 +2393,11 @@ describe(Support.getTestDialectTeaser('Model'), () => {
return
this
.
sequelize
.
queryInterface
.
describeTable
(
'Publics'
,
{
schema
:
'special'
,
logging
(
sql
)
{
if
(
dialect
===
'sqlite'
||
dialect
===
'mysql'
||
dialect
===
'mssql'
||
dialect
===
'mariadb'
)
{
if
(
dialect
===
'sqlite'
&&
sql
.
includes
(
'TABLE_INFO'
))
{
count
++
;
expect
(
sql
).
to
.
contain
(
'special'
);
}
else
if
([
'mysql'
,
'mssql'
,
'mariadb'
].
includes
(
dialect
))
{
expect
(
sql
).
to
.
contain
(
'special'
);
count
++
;
}
...
...
test/integration/model/sync.test.js
View file @
3d2df28
...
...
@@ -184,12 +184,16 @@ describe(Support.getTestDialectTeaser('Model'), () => {
.
then
(
results
=>
{
if
(
dialect
===
'sqlite'
)
{
// SQLite doesn't treat primary key as index
expect
(
results
).
to
.
have
.
length
(
4
);
// However it does create an extra "autoindex", except primary == false
expect
(
results
).
to
.
have
.
length
(
4
+
1
);
}
else
{
expect
(
results
).
to
.
have
.
length
(
4
+
1
);
expect
(
results
.
filter
(
r
=>
r
.
primary
)).
to
.
have
.
length
(
1
);
}
if
(
dialect
===
'sqlite'
)
{
expect
(
results
.
filter
(
r
=>
r
.
name
===
'sqlite_autoindex_testSyncs_1'
)).
to
.
have
.
length
(
1
);
}
expect
(
results
.
filter
(
r
=>
r
.
name
===
'another_index_email_mobile'
)).
to
.
have
.
length
(
1
);
expect
(
results
.
filter
(
r
=>
r
.
name
===
'another_index_phone_mobile'
)).
to
.
have
.
length
(
1
);
expect
(
results
.
filter
(
r
=>
r
.
name
===
'another_index_email'
)).
to
.
have
.
length
(
1
);
...
...
@@ -225,7 +229,8 @@ describe(Support.getTestDialectTeaser('Model'), () => {
.
then
(
results
=>
{
if
(
dialect
===
'sqlite'
)
{
// SQLite doesn't treat primary key as index
expect
(
results
).
to
.
have
.
length
(
4
);
// However it does create an extra "autoindex", except primary == false
expect
(
results
).
to
.
have
.
length
(
4
+
1
);
}
else
{
expect
(
results
).
to
.
have
.
length
(
4
+
1
);
expect
(
results
.
filter
(
r
=>
r
.
primary
)).
to
.
have
.
length
(
1
);
...
...
test/integration/query-interface/changeColumn.test.js
View file @
3d2df28
...
...
@@ -209,7 +209,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
expect
(
firstForeignKeys
.
length
).
to
.
be
.
equal
(
newForeignKeys
.
length
);
expect
(
firstForeignKeys
[
0
].
columnName
).
to
.
be
.
equal
(
'level_id'
);
expect
(
firstForeignKeys
[
0
].
columnName
).
to
.
be
.
equal
(
newForeignKeys
[
0
].
columnName
);
return
this
.
queryInterface
.
describeTable
({
tableName
:
'users'
});
...
...
@@ -237,5 +237,136 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
});
});
}
if
(
dialect
===
'sqlite'
)
{
it
(
'should not remove unique constraints when adding or modifying columns'
,
function
()
{
return
this
.
queryInterface
.
createTable
({
tableName
:
'Foos'
},
{
id
:
{
allowNull
:
false
,
autoIncrement
:
true
,
primaryKey
:
true
,
type
:
DataTypes
.
INTEGER
},
name
:
{
allowNull
:
false
,
unique
:
true
,
type
:
DataTypes
.
STRING
},
email
:
{
allowNull
:
false
,
unique
:
true
,
type
:
DataTypes
.
STRING
}
}).
then
(()
=>
{
return
this
.
queryInterface
.
addColumn
(
'Foos'
,
'phone'
,
{
type
:
DataTypes
.
STRING
,
defaultValue
:
null
,
allowNull
:
true
}).
then
(()
=>
{
return
this
.
queryInterface
.
describeTable
({
tableName
:
'Foos'
}).
then
(
table
=>
{
expect
(
table
.
phone
.
allowNull
).
to
.
equal
(
true
,
'(1) phone column should allow null values'
);
expect
(
table
.
phone
.
defaultValue
).
to
.
equal
(
null
,
'(1) phone column should have a default value of null'
);
expect
(
table
.
email
.
unique
).
to
.
equal
(
true
,
'(1) email column should remain unique'
);
expect
(
table
.
name
.
unique
).
to
.
equal
(
true
,
'(1) name column should remain unique'
);
}).
then
(()
=>
{
return
this
.
queryInterface
.
changeColumn
(
'Foos'
,
'email'
,
{
type
:
DataTypes
.
STRING
,
allowNull
:
true
}).
then
(()
=>
{
return
this
.
queryInterface
.
describeTable
({
tableName
:
'Foos'
}).
then
(
table
=>
{
expect
(
table
.
email
.
allowNull
).
to
.
equal
(
true
,
'(2) email column should allow null values'
);
expect
(
table
.
email
.
unique
).
to
.
equal
(
true
,
'(2) email column should remain unique'
);
expect
(
table
.
name
.
unique
).
to
.
equal
(
true
,
'(2) name column should remain unique'
);
});
});
});
});
});
});
it
(
'should add unique constraints to 2 columns and keep allowNull'
,
function
()
{
return
this
.
queryInterface
.
createTable
({
tableName
:
'Foos'
},
{
id
:
{
allowNull
:
false
,
autoIncrement
:
true
,
primaryKey
:
true
,
type
:
DataTypes
.
INTEGER
},
name
:
{
allowNull
:
false
,
type
:
DataTypes
.
STRING
},
email
:
{
allowNull
:
true
,
type
:
DataTypes
.
STRING
}
}).
then
(()
=>
{
return
this
.
queryInterface
.
changeColumn
(
'Foos'
,
'name'
,
{
type
:
DataTypes
.
STRING
,
unique
:
true
}).
then
(()
=>
{
return
this
.
queryInterface
.
changeColumn
(
'Foos'
,
'email'
,
{
type
:
DataTypes
.
STRING
,
unique
:
true
}).
then
(()
=>
{
return
this
.
queryInterface
.
describeTable
({
tableName
:
'Foos'
}).
then
(
table
=>
{
expect
(
table
.
name
.
allowNull
).
to
.
equal
(
false
);
expect
(
table
.
name
.
unique
).
to
.
equal
(
true
);
expect
(
table
.
email
.
allowNull
).
to
.
equal
(
true
);
expect
(
table
.
email
.
unique
).
to
.
equal
(
true
);
});
});
});
});
});
it
(
'should not remove foreign keys when adding or modifying columns'
,
function
()
{
const
Task
=
this
.
sequelize
.
define
(
'Task'
,
{
title
:
DataTypes
.
STRING
}),
User
=
this
.
sequelize
.
define
(
'User'
,
{
username
:
DataTypes
.
STRING
});
User
.
hasOne
(
Task
);
return
User
.
sync
({
force
:
true
}).
then
(()
=>
{
return
Task
.
sync
({
force
:
true
}).
then
(()
=>
{
return
this
.
queryInterface
.
addColumn
(
'Tasks'
,
'bar'
,
DataTypes
.
INTEGER
).
then
(()
=>
{
return
this
.
queryInterface
.
getForeignKeyReferencesForTable
(
'Tasks'
).
then
(
refs
=>
{
expect
(
refs
.
length
).
to
.
equal
(
1
,
'should keep foreign key after adding column'
);
expect
(
refs
[
0
].
columnName
).
to
.
equal
(
'UserId'
);
expect
(
refs
[
0
].
referencedTableName
).
to
.
equal
(
'Users'
);
expect
(
refs
[
0
].
referencedColumnName
).
to
.
equal
(
'id'
);
}).
then
(()
=>
{
return
this
.
queryInterface
.
changeColumn
(
'Tasks'
,
'bar'
,
DataTypes
.
STRING
).
then
(()
=>
{
return
this
.
queryInterface
.
getForeignKeyReferencesForTable
(
'Tasks'
).
then
(
refs
=>
{
expect
(
refs
.
length
).
to
.
equal
(
1
,
'should keep foreign key after changing column'
);
expect
(
refs
[
0
].
columnName
).
to
.
equal
(
'UserId'
);
expect
(
refs
[
0
].
referencedTableName
).
to
.
equal
(
'Users'
);
expect
(
refs
[
0
].
referencedColumnName
).
to
.
equal
(
'id'
);
}).
then
(()
=>
{
return
this
.
queryInterface
.
renameColumn
(
'Tasks'
,
'bar'
,
'foo'
).
then
(()
=>
{
return
this
.
queryInterface
.
getForeignKeyReferencesForTable
(
'Tasks'
).
then
(
refs
=>
{
expect
(
refs
.
length
).
to
.
equal
(
1
,
'should keep foreign key after renaming column'
);
expect
(
refs
[
0
].
columnName
).
to
.
equal
(
'UserId'
);
expect
(
refs
[
0
].
referencedTableName
).
to
.
equal
(
'Users'
);
expect
(
refs
[
0
].
referencedColumnName
).
to
.
equal
(
'id'
);
});
});
});
});
});
});
});
});
});
}
});
});
test/unit/dialects/sqlite/query-generator.test.js
View file @
3d2df28
...
...
@@ -606,7 +606,7 @@ if (dialect === 'sqlite') {
title
:
'Properly quotes column names'
,
arguments
:
[
'myTable'
,
'foo'
,
'commit'
,
{
commit
:
'VARCHAR(255)'
,
bar
:
'VARCHAR(255)'
}],
expectation
:
'CREATE T
EMPORARY T
ABLE IF NOT EXISTS `myTable_backup` (`commit` VARCHAR(255), `bar` VARCHAR(255));'
+
'CREATE TABLE IF NOT EXISTS `myTable_backup` (`commit` VARCHAR(255), `bar` VARCHAR(255));'
+
'INSERT INTO `myTable_backup` SELECT `foo` AS `commit`, `bar` FROM `myTable`;'
+
'DROP TABLE `myTable`;'
+
'CREATE TABLE IF NOT EXISTS `myTable` (`commit` VARCHAR(255), `bar` VARCHAR(255));'
+
...
...
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