fix: SQLite simple-enum column parsing (#10550)

This commit is contained in:
Szymon Bretner 2023-12-29 15:16:33 +01:00 committed by GitHub
parent a4900ae15f
commit 696e688d00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 153 additions and 13 deletions

View File

@ -1453,20 +1453,10 @@ export abstract class AbstractSqliteQueryRunner
}
if (tableColumn.type === "varchar") {
// Check if this is an enum
const enumMatch = sql.match(
new RegExp(
'"(' +
tableColumn.name +
")\" varchar CHECK\\s*\\(\\s*\"\\1\"\\s+IN\\s*\\(('[^']+'(?:\\s*,\\s*'[^']+')+)\\s*\\)\\s*\\)",
),
tableColumn.enum = OrmUtils.parseSqlCheckExpression(
sql,
tableColumn.name,
)
if (enumMatch) {
// This is an enum
tableColumn.enum = enumMatch[2]
.substr(1, enumMatch[2].length - 2)
.split("','")
}
}
// parse datatype and attempt to retrieve length, precision and scale

View File

@ -340,6 +340,81 @@ export class OrmUtils {
return !haveSharedObjects
}
/**
* Parses the CHECK constraint on the specified column and returns
* all values allowed by the constraint or undefined if the constraint
* is not present.
*/
static parseSqlCheckExpression(
sql: string,
columnName: string,
): string[] | undefined {
const enumMatch = sql.match(
new RegExp(
`"${columnName}" varchar CHECK\\s*\\(\\s*"${columnName}"\\s+IN\\s*`,
),
)
if (enumMatch && enumMatch.index) {
const afterMatch = sql.substring(
enumMatch.index + enumMatch[0].length,
)
// This is an enum
// all enum values stored as a comma separated list
const chars = afterMatch
/**
* * When outside quotes: empty string
* * When inside single quotes: `'`
*/
let currentQuotes = ""
let nextValue = ""
const enumValues: string[] = []
for (let idx = 0; idx < chars.length; idx++) {
const char = chars[idx]
switch (char) {
case ",":
if (currentQuotes == "") {
enumValues.push(nextValue)
nextValue = ""
} else {
nextValue += char
}
break
case "'":
if (currentQuotes == char) {
const isNextCharQuote = chars[idx + 1] === char
if (isNextCharQuote) {
// double quote in sql should be treated as a
// single quote that's part of the quoted string
nextValue += char
idx += 1 // skip that next quote
} else {
currentQuotes = ""
}
} else {
currentQuotes = char
}
break
case ")":
if (currentQuotes == "") {
enumValues.push(nextValue)
return enumValues
} else {
nextValue += char
}
break
default:
if (currentQuotes != "") {
nextValue += char
}
}
}
}
return undefined
}
// -------------------------------------------------------------------------
// Private methods
// -------------------------------------------------------------------------

75
test/unit/orm-utils.ts Normal file
View File

@ -0,0 +1,75 @@
import { OrmUtils } from "../../src/util/OrmUtils"
import { expect } from "chai"
describe(`orm-utils`, () => {
describe("parseSqlCheckExpression", () => {
it("parses a simple CHECK constraint", () => {
// Spaces between CHECK values
expect(
OrmUtils.parseSqlCheckExpression(
`CREATE TABLE "foo_table" (
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"col" varchar CHECK("col" IN ('FOO', 'BAR', 'BAZ')) NOT NULL,
"some_other_col" integer NOT NULL
);`,
"col",
),
).to.have.same.members(["FOO", "BAR", "BAZ"])
// No spaces between CHECK values
expect(
OrmUtils.parseSqlCheckExpression(
`CREATE TABLE "foo_table" (
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"col" varchar CHECK("col" IN ('FOO','BAR','BAZ')) NOT NULL,
"some_other_col" integer NOT NULL
);`,
"col",
),
).to.have.same.members(["FOO", "BAR", "BAZ"])
})
it("returns undefined when the column doesn't have a CHECK", () => {
expect(
OrmUtils.parseSqlCheckExpression(
`CREATE TABLE "foo_table" (
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"col" varchar NOT NULL,
"some_other_col" integer NOT NULL
);`,
"col",
),
).to.equal(undefined)
})
it("parses a CHECK constraint with values containing special characters", () => {
expect(
OrmUtils.parseSqlCheckExpression(
`CREATE TABLE "foo_table" (
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"col" varchar CHECK("col" IN (
'a,b',
',c,',
'd''d',
'''e''',
'f'',''f',
''')',
')'''
)
) NOT NULL,
"some_other_col" integer NOT NULL
);`,
"col",
),
).to.have.same.members([
"a,b",
",c,",
"d'd",
"'e'",
"f','f",
"')",
")'",
])
})
})
})