Skip to content

Commit

Permalink
SQLite parser improvements (#4333)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickrum committed Mar 2, 2021
1 parent a98614d commit 3e6176a
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 111 deletions.
6 changes: 5 additions & 1 deletion lib/dialects/sqlite3/schema/ddl.js
Expand Up @@ -265,9 +265,13 @@ class SQLite3_DDL {
}
: null;

column.constraints.not = newColumnInfo.notNull
column.constraints.notnull = newColumnInfo.notNull
? { name: null, conflict: null }
: null;

column.constraints.null = newColumnInfo.notNull
? null
: column.constraints.null;
}

return column;
Expand Down
25 changes: 17 additions & 8 deletions lib/dialects/sqlite3/schema/internal/compiler.js
Expand Up @@ -40,16 +40,19 @@ function typeName(ast, wrap) {
}

function columnConstraintList(ast, wrap) {
return `${primaryColumnConstraint(ast, wrap)}${notColumnConstraint(
return `${primaryColumnConstraint(ast, wrap)}${notnullColumnConstraint(
ast,
wrap
)}${uniqueColumnConstraint(ast, wrap)}${checkColumnConstraint(
)}${nullColumnConstraint(ast, wrap)}${uniqueColumnConstraint(
ast,
wrap
)}${defaultColumnConstraint(ast, wrap)}${collateColumnConstraint(
)}${checkColumnConstraint(ast, wrap)}${defaultColumnConstraint(
ast,
wrap
)}${referencesColumnConstraint(ast, wrap)}${asColumnConstraint(ast, wrap)}`;
)}${collateColumnConstraint(ast, wrap)}${referencesColumnConstraint(
ast,
wrap
)}${asColumnConstraint(ast, wrap)}`;
}

function primaryColumnConstraint(ast, wrap) {
Expand All @@ -65,15 +68,21 @@ function autoincrement(ast, wrap) {
return ast.autoincrement ? ' AUTOINCREMENT' : '';
}

function notColumnConstraint(ast, wrap) {
return ast.not !== null
? ` ${constraintName(ast.not, wrap)}NOT NULL${conflictClause(
ast.not,
function notnullColumnConstraint(ast, wrap) {
return ast.notnull !== null
? ` ${constraintName(ast.notnull, wrap)}NOT NULL${conflictClause(
ast.notnull,
wrap
)}`
: '';
}

function nullColumnConstraint(ast, wrap) {
return ast.null !== null
? ` ${constraintName(ast.null, wrap)}NULL${conflictClause(ast.null, wrap)}`
: '';
}

function uniqueColumnConstraint(ast, wrap) {
return ast.unique !== null
? ` ${constraintName(ast.unique, wrap)}UNIQUE${conflictClause(
Expand Down
29 changes: 19 additions & 10 deletions lib/dialects/sqlite3/schema/internal/parser.js
Expand Up @@ -2,7 +2,7 @@ const { tokenize } = require('./tokenizer');
const { s, a, m, o, l, n, t, e, f } = require('./parser-combinator');

const TOKENS = {
keyword: /(?:ABORT|ADD|AFTER|ALL|ALTER|ANALYZE|AND|AS|ASC|ATTACH|AUTOINCREMENT|BEFORE|BEGIN|BETWEEN|BY|CASCADE|CASE|CAST|CHECK|COLLATE|COLUMN|COMMIT|CONFLICT|CONSTRAINT|CREATE|CROSS|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|DATABASE|DEFAULT|DEFERRED|DEFERRABLE|DELETE|DESC|DETACH|DISTINCT|DROP|END|EACH|ELSE|ESCAPE|EXCEPT|EXCLUSIVE|EXISTS|EXPLAIN|FAIL|FOR|FOREIGN|FROM|FULL|GLOB|GROUP|HAVING|IF|IGNORE|IMMEDIATE|IN|INDEX|INITIALLY|INNER|INSERT|INSTEAD|INTERSECT|INTO|IS|ISNULL|JOIN|KEY|LEFT|LIKE|LIMIT|MATCH|NATURAL|NOT|NOTNULL|NULL|OF|OFFSET|ON|OR|ORDER|OUTER|PLAN|PRAGMA|PRIMARY|QUERY|RAISE|REFERENCES|REGEXP|REINDEX|RENAME|REPLACE|RESTRICT|RIGHT|ROLLBACK|ROW|SELECT|SET|TABLE|TEMP|TEMPORARY|THEN|TO|TRANSACTION|TRIGGER|UNION|UNIQUE|UPDATE|USING|VACUUM|VALUES|VIEW|VIRTUAL|WHEN|WHERE)(?=\s+|-|\(|\)|;|\+|\*|\/|%|==|=|<=|<>|<<|<|>=|>>|>|!=|,|&|~|\|\||\||\.)/,
keyword: /(?:ABORT|ACTION|ADD|AFTER|ALL|ALTER|ALWAYS|ANALYZE|AND|AS|ASC|ATTACH|AUTOINCREMENT|BEFORE|BEGIN|BETWEEN|BY|CASCADE|CASE|CAST|CHECK|COLLATE|COLUMN|COMMIT|CONFLICT|CONSTRAINT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|DATABASE|DEFAULT|DEFERRED|DEFERRABLE|DELETE|DESC|DETACH|DISTINCT|DO|DROP|END|EACH|ELSE|ESCAPE|EXCEPT|EXCLUSIVE|EXCLUDE|EXISTS|EXPLAIN|FAIL|FILTER|FIRST|FOLLOWING|FOR|FOREIGN|FROM|FULL|GENERATED|GLOB|GROUP|GROUPS|HAVING|IF|IGNORE|IMMEDIATE|IN|INDEX|INDEXED|INITIALLY|INNER|INSERT|INSTEAD|INTERSECT|INTO|IS|ISNULL|JOIN|KEY|LAST|LEFT|LIKE|LIMIT|MATCH|MATERIALIZED|NATURAL|NO|NOT|NOTHING|NOTNULL|NULL|NULLS|OF|OFFSET|ON|OR|ORDER|OTHERS|OUTER|OVER|PARTITION|PLAN|PRAGMA|PRECEDING|PRIMARY|QUERY|RAISE|RANGE|RECURSIVE|REFERENCES|REGEXP|REINDEX|RELEASE|RENAME|REPLACE|RESTRICT|RETURNING|RIGHT|ROLLBACK|ROW|ROWS|SAVEPOINT|SELECT|SET|TABLE|TEMP|TEMPORARY|THEN|TIES|TO|TRANSACTION|TRIGGER|UNBOUNDED|UNION|UNIQUE|UPDATE|USING|VACUUM|VALUES|VIEW|VIRTUAL|WHEN|WHERE|WINDOW|WITH|WITHOUT)(?=\s+|-|\(|\)|;|\+|\*|\/|%|==|=|<=|<>|<<|<|>=|>>|>|!=|,|&|~|\|\||\||\.)/,
id: /"[^"]*(?:""[^"]*)*"|`[^`]*(?:``[^`]*)*`|\[[^[\]]*\]|[a-z_][a-z0-9_$]*/,
string: /'[^']*(?:''[^']*)*'/,
blob: /x'(?:[0-9a-f][0-9a-f])+'/,
Expand All @@ -17,10 +17,10 @@ function parseCreateTable(sql) {

if (!result.success) {
throw new Error(
`Parsing CREATE TABLE failed at: [${result.input
`Parsing CREATE TABLE failed at [${result.input
.slice(result.index)
.map((t) => t.text)
.join(' ')}]`
.join(' ')}] of "${sql}"`
);
}

Expand All @@ -32,10 +32,10 @@ function parseCreateIndex(sql) {

if (!result.success) {
throw new Error(
`Parsing CREATE INDEX failed at: [${result.input
`Parsing CREATE INDEX failed at [${result.input
.slice(result.index)
.map((t) => t.text)
.join(' ')}]`
.join(' ')}] of "${sql}"`
);
}

Expand Down Expand Up @@ -94,7 +94,7 @@ function typeName(ctx) {
return o(
s(
[
m(n({ do: t({ type: 'id' }), not: t({ text: 'GENERATED' }) })),
m(t({ type: 'id' })),
a([
s(
[
Expand Down Expand Up @@ -124,7 +124,8 @@ function columnConstraintList(ctx) {
constraints: Object.assign(
{
primary: null,
not: null,
notnull: null,
null: null,
unique: null,
check: null,
default: null,
Expand All @@ -140,7 +141,8 @@ function columnConstraintList(ctx) {
function columnConstraint(ctx) {
return a([
primaryColumnConstraint,
notColumnConstraint,
notnullColumnConstraint,
nullColumnConstraint,
uniqueColumnConstraint,
checkColumnConstraint,
defaultColumnConstraint,
Expand Down Expand Up @@ -170,15 +172,22 @@ function autoincrement(ctx) {
}))(ctx);
}

function notColumnConstraint(ctx) {
function notnullColumnConstraint(ctx) {
return s(
[
constraintName,
t({ text: 'NOT' }, (v) => null),
t({ text: 'NULL' }, (v) => null),
conflictClause,
],
(v) => ({ not: Object.assign({}, ...v.filter((x) => x !== null)) })
(v) => ({ notnull: Object.assign({}, ...v.filter((x) => x !== null)) })
)(ctx);
}

function nullColumnConstraint(ctx) {
return s(
[constraintName, t({ text: 'NULL' }, (v) => null), conflictClause],
(v) => ({ null: Object.assign({}, ...v.filter((x) => x !== null)) })
)(ctx);
}

Expand Down
31 changes: 26 additions & 5 deletions test/integration2/schema/alter.spec.js
Expand Up @@ -38,13 +38,16 @@ describe('Schema', () => {
.dateTime('column_defaultToAndNotNullable')
.defaultTo(0)
.notNullable();

table.boolean('column_nullable').nullable();
});

await knex('alter_table').insert({
column_integer: 1,
column_string: '1',
column_datetime: 1614349736,
column_notNullable: 'text',
column_nullable: true,
});
});

Expand All @@ -67,7 +70,7 @@ describe('Schema', () => {
expect(item_one.column_string).to.be.a('number');
expect(item_one.column_datetime).to.be.a('number');
expect(tableAfter).to.equal(
"CREATE TABLE \"alter_table\" (`column_integer` varchar(255), `column_string` integer, `column_datetime` date, `column_defaultTo` integer DEFAULT '0', `column_notNullable` varchar(255) NOT NULL, `column_defaultToAndNotNullable` datetime NOT NULL DEFAULT '0')"
"CREATE TABLE \"alter_table\" (`column_integer` varchar(255), `column_string` integer, `column_datetime` date, `column_defaultTo` integer DEFAULT '0', `column_notNullable` varchar(255) NOT NULL, `column_defaultToAndNotNullable` datetime NOT NULL DEFAULT '0', `column_nullable` boolean NULL)"
);
});

Expand All @@ -92,14 +95,14 @@ describe('Schema', () => {

expect(item_two.column_integer).to.equal(0);
expect(item_two.column_datetime).to.equal(0);
expect(
await expect(
knex('alter_table').insert({ column_notNullable: 'text' })
).to.be.rejectedWith(
Error,
"insert into `alter_table` (`column_notNullable`) values ('text') - SQLITE_CONSTRAINT: NOT NULL constraint failed: alter_table.column_string"
);
expect(tableAfter).to.equal(
"CREATE TABLE \"alter_table\" (`column_integer` integer DEFAULT '0', `column_string` varchar(255) NOT NULL, `column_datetime` datetime NOT NULL DEFAULT '0', `column_defaultTo` integer DEFAULT '0', `column_notNullable` varchar(255) NOT NULL, `column_defaultToAndNotNullable` datetime NOT NULL DEFAULT '0')"
"CREATE TABLE \"alter_table\" (`column_integer` integer DEFAULT '0', `column_string` varchar(255) NOT NULL, `column_datetime` datetime NOT NULL DEFAULT '0', `column_defaultTo` integer DEFAULT '0', `column_notNullable` varchar(255) NOT NULL, `column_defaultToAndNotNullable` datetime NOT NULL DEFAULT '0', `column_nullable` boolean NULL)"
);
});

Expand All @@ -119,7 +122,25 @@ describe('Schema', () => {
expect(item_two.column_notNullable).to.be.null;
expect(item_two.column_defaultToAndNotNullable).to.be.null;
expect(tableAfter).to.equal(
'CREATE TABLE "alter_table" (`column_integer` integer, `column_string` varchar(255), `column_datetime` datetime, `column_defaultTo` integer, `column_notNullable` varchar(255), `column_defaultToAndNotNullable` datetime)'
'CREATE TABLE "alter_table" (`column_integer` integer, `column_string` varchar(255), `column_datetime` datetime, `column_defaultTo` integer, `column_notNullable` varchar(255), `column_defaultToAndNotNullable` datetime, `column_nullable` boolean NULL)'
);
});

it('removes an existing null constraint if a not null constraint is added to a column', async () => {
await knex.schema.alterTable('alter_table', (table) => {
table.boolean('column_nullable').notNullable().alter();
});

const tableAfter = (await knex.raw(QUERY_TABLE))[0].sql;

await expect(
knex('alter_table').insert({ column_notNullable: 'text' })
).to.be.rejectedWith(
Error,
"insert into `alter_table` (`column_notNullable`) values ('text') - SQLITE_CONSTRAINT: NOT NULL constraint failed: alter_table.column_nullable"
);
expect(tableAfter).to.equal(
"CREATE TABLE \"alter_table\" (`column_integer` integer, `column_string` varchar(255), `column_datetime` datetime, `column_defaultTo` integer DEFAULT '0', `column_notNullable` varchar(255) NOT NULL, `column_defaultToAndNotNullable` datetime NOT NULL DEFAULT '0', `column_nullable` boolean NOT NULL)"
);
});

Expand All @@ -131,7 +152,7 @@ describe('Schema', () => {
const queries = await builder.generateDdlCommands();

expect(queries.sql).to.deep.equal([
"CREATE TABLE `_knex_temp_alter111` (`column_integer` varchar(255), `column_string` varchar(255), `column_datetime` datetime, `column_defaultTo` integer DEFAULT '0', `column_notNullable` varchar(255) NOT NULL, `column_defaultToAndNotNullable` datetime NOT NULL DEFAULT '0')",
"CREATE TABLE `_knex_temp_alter111` (`column_integer` varchar(255), `column_string` varchar(255), `column_datetime` datetime, `column_defaultTo` integer DEFAULT '0', `column_notNullable` varchar(255) NOT NULL, `column_defaultToAndNotNullable` datetime NOT NULL DEFAULT '0', `column_nullable` boolean NULL)",
'INSERT INTO _knex_temp_alter111 SELECT * FROM alter_table;',
'DROP TABLE "alter_table"',
'ALTER TABLE "_knex_temp_alter111" RENAME TO "alter_table"',
Expand Down

0 comments on commit 3e6176a

Please sign in to comment.