Skip to content

Commit

Permalink
feat: support table constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
invisal committed Feb 10, 2024
1 parent 0a4e491 commit 26441f1
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 26 deletions.
7 changes: 5 additions & 2 deletions src/drivers/DatabaseDriver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ export type DatabaseColumnConflict =
| "REPLACE";

export interface DatabaseForeignKeyCaluse {
tableName: string;
column: string[];
foreignTableName: string;
foreignColumns: string[];
columns?: string[];
}

export interface DatabaseTableColumnConstraint {
name?: string;

primaryKey?: boolean;
primaryColumns?: string[];
primaryKeyOrder?: "ASC" | "DESC";
primaryKeyConflict?: DatabaseColumnConflict;
autoIncrement?: boolean;
Expand Down Expand Up @@ -58,6 +60,7 @@ export interface DatabaseTableSchema {
pk: string[];
autoIncrement: boolean;
tableName?: string;
constraints?: DatabaseTableColumnConstraint[];
}

export default class DatabaseDriver {
Expand Down
71 changes: 67 additions & 4 deletions src/lib/sql-parse-table.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ it("parse column constraint", () => {
autoIncrement: true,
} as DatabaseTableColumnConstraint);

expect(pcc("primary key(first_name, last_name)")).toEqual({
primaryKey: true,
autoIncrement: false,
primaryColumns: ["first_name", "last_name"],
} as DatabaseTableColumnConstraint);

expect(pcc("primary key on conflict rollback not null")).toEqual({
primaryKey: true,
autoIncrement: false,
Expand Down Expand Up @@ -65,10 +71,20 @@ it("parse column constraint", () => {
defaultExpression: `(round(julianday('now'))`,
} as DatabaseTableColumnConstraint);

expect(
pcc(`foreign key ("user_id") references "users" on delete cascade ("id")`)
).toEqual({
foreignKey: {
columns: ["user_id"],
foreignTableName: "users",
foreignColumns: ["id"],
},
} as DatabaseTableColumnConstraint);

expect(pcc(`references "users" on delete cascade ("id")`)).toEqual({
foreignKey: {
tableName: "users",
column: ["id"],
foreignTableName: "users",
foreignColumns: ["id"],
},
} as DatabaseTableColumnConstraint);

Expand All @@ -92,6 +108,7 @@ it("parse create table", () => {
tableName: "invoice_detail",
pk: ["id"],
autoIncrement: true,
constraints: [],
columns: [
{
name: "id",
Expand All @@ -107,8 +124,8 @@ it("parse create table", () => {
type: "integer",
constraint: {
foreignKey: {
tableName: "product",
column: ["id"],
foreignTableName: "product",
foreignColumns: ["id"],
},
},
},
Expand Down Expand Up @@ -138,3 +155,49 @@ it("parse create table", () => {
],
} as DatabaseTableSchema);
});

it("parse create table with table constraints", () => {
const sql = `create table "users"(
first_name varchar,
last_name varchar,
category_id integer,
primary key(first_name, last_name),
foreign key(category_id) references category(id)
);`;

expect(p(sql)).toEqual({
tableName: "users",
pk: ["first_name", "last_name"],
autoIncrement: false,
constraints: [
{
primaryKey: true,
autoIncrement: false,
primaryColumns: ["first_name", "last_name"],
},
{
foreignKey: {
columns: ["category_id"],
foreignColumns: ["id"],
foreignTableName: "category",
},
},
],
columns: [
{
name: "first_name",
type: "varchar",
pk: true,
},
{
name: "last_name",
type: "varchar",
pk: true,
},
{
name: "category_id",
type: "integer",
},
],
} as DatabaseTableSchema);
});
92 changes: 72 additions & 20 deletions src/lib/sql-parse-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,19 @@ function parseConstraintConflict(
}
}

export function parseColumnList(columnPtr: Cursor) {
const columns: string[] = [];

while (!columnPtr.end()) {
columns.push(columnPtr.consumeIdentifier());

if (!columnPtr.match(",")) break;
columnPtr.next();
}

return columns;
}

export function parseColumnConstraint(
cursor: Cursor
): DatabaseTableColumnConstraint | undefined {
Expand All @@ -169,6 +182,7 @@ export function parseColumnConstraint(
};
} else if (cursor.matchKeyword("PRIMARY")) {
let primaryKeyOrder: "ASC" | "DESC" | undefined;
let primaryColumns: string[] | undefined;
let autoIncrement = false;

cursor.next();
Expand All @@ -177,6 +191,12 @@ export function parseColumnConstraint(

cursor.next();

const parens = cursor.enterParens();
if (parens) {
primaryColumns = parseColumnList(parens);
cursor.next();
}

if (cursor.matchKeyword("ASC")) {
primaryKeyOrder = "ASC";
cursor.next();
Expand All @@ -195,6 +215,7 @@ export function parseColumnConstraint(
return {
primaryKey: true,
primaryKeyOrder,
primaryColumns,
autoIncrement,
primaryKeyConflict: conflict,
...parseColumnConstraint(cursor),
Expand Down Expand Up @@ -251,10 +272,31 @@ export function parseColumnConstraint(
};
} else if (cursor.matchKeyword("CHECK")) {
} else if (cursor.matchKeyword("COLLATE")) {
} else if (cursor.matchKeyword("FOREIGN")) {
cursor.next();

if (!cursor.match("KEY")) throw new Error("FOREIGN should follow by KEY");
cursor.next();

const parens = cursor.enterParens();

if (!parens) throw new Error("FOREIGN KEY should follow by column list");

const columns = parseColumnList(parens);
cursor.next();
const refConstraint = parseColumnConstraint(cursor);

return {
foreignKey: {
foreignTableName: refConstraint?.foreignKey?.foreignTableName ?? "",
foreignColumns: refConstraint?.foreignKey?.foreignColumns ?? [],
columns,
},
};
} else if (cursor.matchKeyword("REFERENCES")) {
cursor.next();
const foreignTableName = cursor.consumeIdentifier();
const foreignColumns: string[] = [];
let foreignColumns: string[] = [];

// Trying to find the parens by skipping all other rule
// We may visit more rule in the future, but at the moment
Expand All @@ -270,18 +312,13 @@ export function parseColumnConstraint(
const columnPtr = cursor.enterParens();

if (columnPtr) {
while (!columnPtr.end()) {
foreignColumns.push(columnPtr.consumeIdentifier());

if (!columnPtr.match(",")) break;
columnPtr.next();
}
foreignColumns = parseColumnList(columnPtr);
}

return {
foreignKey: {
tableName: foreignTableName,
column: foreignColumns,
foreignTableName,
foreignColumns,
},
...parseColumnConstraint(cursor),
};
Expand Down Expand Up @@ -312,15 +349,13 @@ export function parseColumnConstraint(
return undefined;
}

function parseTableConstraint(cursor: Cursor) {
return null;
}

function parseTableDefinition(cursor: Cursor): {
columns: DatabaseTableColumn[];
constraints: DatabaseTableColumnConstraint[];
} {
let moveNext = true;
const columns: DatabaseTableColumn[] = [];
const constraints: DatabaseTableColumnConstraint[] = [];

while (moveNext) {
moveNext = false;
Expand All @@ -334,8 +369,11 @@ function parseTableDefinition(cursor: Cursor): {
"FOREIGN",
])
) {
const constraint = parseTableConstraint(cursor);
if (constraint) moveNext = true;
const constraint = parseColumnConstraint(cursor);
if (constraint) {
constraints.push(constraint);
moveNext = true;
}
} else {
const column = parseColumnDef(cursor);
if (column) {
Expand All @@ -352,7 +390,21 @@ function parseTableDefinition(cursor: Cursor): {
cursor.next();
}

return { columns };
for (const constraint of constraints) {
if (constraint.primaryKey && constraint.primaryColumns) {
for (const pkColumn of constraint.primaryColumns) {
const column = columns.find(
(col) => pkColumn.toLowerCase() === col.name.toLowerCase()
);

if (column) {
column.pk = true;
}
}
}
}

return { columns, constraints };
}

// Our parser follows this spec
Expand All @@ -374,11 +426,11 @@ export function parseCreateTableScript(sql: string): DatabaseTableSchema {
const tableName = cursor.consumeIdentifier();

const defCursor = cursor.enterParens();
const defs = defCursor ? parseTableDefinition(defCursor) : { columns: [] };
const defs = defCursor
? parseTableDefinition(defCursor)
: { columns: [], constraints: [] };

const pk = defs.columns
.filter((col) => col.constraint?.primaryKey)
.map((col) => col.name);
const pk = defs.columns.filter((col) => col.pk).map((col) => col.name);

const autoIncrement = defs.columns.some(
(col) => !!col.constraint?.autoIncrement
Expand Down

0 comments on commit 26441f1

Please sign in to comment.