diff --git a/README.md b/README.md index 28f0270..c63109b 100644 --- a/README.md +++ b/README.md @@ -73,22 +73,30 @@ sql3foreignkey *sql3table_constraint_foreignkey_clause (sql3tableconstraint sql3string *sql3column_name (sql3column *column); sql3string *sql3column_type (sql3column *column); sql3string *sql3column_length (sql3column *column); -sql3string *sql3column_constraint_name (sql3column *column); sql3string *sql3column_comment (sql3column *column); bool sql3column_is_primarykey (sql3column *column); bool sql3column_is_autoincrement (sql3column *column); bool sql3column_is_notnull (sql3column *column); bool sql3column_is_unique (sql3column *column); +sql3string *sql3column_pk_constraint_name (sql3column *column); sql3order_clause sql3column_pk_order (sql3column *column); sql3conflict_clause sql3column_pk_conflictclause (sql3column *column); +sql3string *sql3column_notnull_constraint_name (sql3column *column); sql3conflict_clause sql3column_notnull_conflictclause (sql3column *column); +sql3string *sql3column_unique_constraint_name (sql3column *column); sql3conflict_clause sql3column_unique_conflictclause (sql3column *column); -sql3string *sql3column_check_expr (sql3column *column); +size_t sql3column_num_check_constraints (sql3column *column); +sql3string *sql3column_check_constraint_name (sql3column *column, size_t index); +sql3string *sql3column_check_expr (sql3column *column, size_t index); +sql3string *sql3column_default_constraint_name (sql3column *column); sql3string *sql3column_default_expr (sql3column *column); +sql3string *sql3column_collate_constraint_name (sql3column *column); sql3string *sql3column_collate_name (sql3column *column); +sql3string *sql3column_foreignkey_constraint_name (sql3column *column); sql3foreignkey *sql3column_foreignkey_clause (sql3column *column); -sql3string *sql3column_generated_expr (sql3column* column); -sql3gen_type sql3column_generated_type (sql3column* column); +sql3string *sql3column_generated_constraint_name (sql3column *column); +sql3string *sql3column_generated_expr (sql3column *column); +sql3gen_type sql3column_generated_type (sql3column *column); // Foreign key sql3string *sql3foreignkey_table (sql3foreignkey *fk); diff --git a/debug/main.c b/debug/main.c index 0e4c6bb..4a78548 100644 --- a/debug/main.c +++ b/debug/main.c @@ -55,7 +55,13 @@ int main (void) { , test -- test 123\n\ INTEGER, UNIQUE (flags /* Hello World*/, test) -- This is another table comment\n\ );"); - test_case("CREATE TABLE Sales(Price INT, Qty INT, Total INT GENERATED ALWAYS AS (Price*Qty) VIRTUAL, Item TEXT)"); + test_case("CREATE TABLE Sales(Price INT, Qty INT, Total INT GENERATED ALWAYS AS (Price*Qty) VIRTUAL, Item TEXT);"); + test_case("CREATE TABLE Constraints(\ + PK INTEGER CONSTRAINT 'PrimaryKey' PRIMARY KEY CONSTRAINT 'NotNull' NOT NULL CONSTRAINT 'Unique' UNIQUE\ + CONSTRAINT 'Check' CHECK (PK>0) CONSTRAINT 'Default' DEFAULT 2 CONSTRAINT 'Collate' COLLATE NOCASE,\ + FK INTEGER CONSTRAINT 'ForeignKey' REFERENCES ForeignTable (Id),\ + GEN INTEGER CONSTRAINT 'Generated' AS (abs(PK)));"); + test_case("CREATE TABLE ColumnChecks(Num INT CONSTRAINT 'GT' CHECK (Num>0) CONSTRAINT 'LT' CHECK (Num<10) CONSTRAINT 'NE' CHECK(Num<>5));"); // https://www.sqlite.org/lang_altertable.html test_case("ALTER TABLE foo RENAME TO bar"); diff --git a/debug/sql3parse_debug.c b/debug/sql3parse_debug.c index 8db427a..638ed19 100644 --- a/debug/sql3parse_debug.c +++ b/debug/sql3parse_debug.c @@ -117,69 +117,105 @@ static void sql3column_dump (sql3column *column) { // column name sql3string *ptr = sql3column_name(column); sql3string_dump(ptr, "Column Name"); - + // column type ptr = sql3column_type(column); sql3string_dump(ptr, "Column Type"); - + // column length ptr = sql3column_length(column); sql3string_dump(ptr, "Column Length"); - - // constraint name - ptr = sql3column_constraint_name(column); - sql3string_dump(ptr, "Constraint Name"); - + // column comment ptr = sql3column_comment(column); if (ptr) sql3string_dump(ptr, "Column Comment"); - + // flags printf("Primary Key: %d\n", sql3column_is_primarykey(column)); printf("Autoincrement: %d\n", sql3column_is_autoincrement(column)); printf("Not NULL: %d\n", sql3column_is_notnull(column)); printf("Unique: %d\n", sql3column_is_unique(column)); - - // enums + + // primary key constraint name + ptr = sql3column_pk_constraint_name(column); + sql3string_dump(ptr, "Primary Key Constraint Name"); + + // primary key order sql3order_clause order = sql3column_pk_order(column); if (order != SQL3ORDER_NONE) printf("Primary Key Order: %s\n", (order == SQL3ORDER_ASC) ? "ASC" : "DESC"); - + + // primary key conflict clause sql3conflict_clause clause = sql3column_pk_conflictclause(column); if (clause != SQL3CONFLICT_NONE) printf("Primary Key Conflict Cause: %s\n", sql3conflict_clause_str(clause)); - + + // not null constraint name + ptr = sql3column_notnull_constraint_name(column); + sql3string_dump(ptr, "Not NULL Constraint Name"); + + // not null conflict clause clause = sql3column_notnull_conflictclause(column); if (clause != SQL3CONFLICT_NONE) printf("Not NULL Conflict Cause: %s\n", sql3conflict_clause_str(clause)); - + + // unique constraint name + ptr = sql3column_unique_constraint_name(column); + sql3string_dump(ptr, "Unique Constraint Name"); + + // unique conflict clause clause = sql3column_unique_conflictclause(column); if (clause != SQL3CONFLICT_NONE) printf("Unique Conflict Cause: %s\n", sql3conflict_clause_str(clause)); - sql3gen_type gen_type = sql3column_generated_type(column); - if (gen_type != SQL3GENTYPE_NONE) - printf("Generated Type: %s\n", sql3generated_type_str(gen_type)); - - // check expr - ptr = sql3column_check_expr(column); - sql3string_dump(ptr, "Check Expression"); - + // check constraints + size_t num_check_constraints = sql3column_num_check_constraints(column); + for (size_t i = 0; i < num_check_constraints; ++i) { + // check constraint name + ptr = sql3column_check_constraint_name(column, i); + sql3string_dump(ptr, "Check Constraint Name"); + + // check expr + ptr = sql3column_check_expr(column, i); + sql3string_dump(ptr, "Check Expression"); + } + + // default constraint name + ptr = sql3column_default_constraint_name(column); + sql3string_dump(ptr, "Default Constraint Name"); + // default expr ptr = sql3column_default_expr(column); sql3string_dump(ptr, "Default Expression"); - + + // collate constraint name + ptr = sql3column_collate_constraint_name(column); + sql3string_dump(ptr, "Collate Constraint Name"); + // collate name ptr = sql3column_collate_name(column); sql3string_dump(ptr, "Collate"); - + + // foreign key constraint name + ptr = sql3column_foreignkey_constraint_name(column); + sql3string_dump(ptr, "Foreign Key Constraint Name"); + // foreign key sql3foreignkey *fk = sql3column_foreignkey_clause(column); sql3foreignkey_dump(fk); + // generated constraint name + ptr = sql3column_generated_constraint_name(column); + sql3string_dump(ptr, "Generated Constraint Name"); + // generated expr ptr = sql3column_generated_expr(column); sql3string_dump(ptr, "Generated Expression"); + + // generated type + sql3gen_type gen_type = sql3column_generated_type(column); + if (gen_type != SQL3GENTYPE_NONE) + printf("Generated Type: %s\n", sql3generated_type_str(gen_type)); } static void sql3tableconstraint_dump (sql3tableconstraint *constraint) { diff --git a/sql3parse_table.c b/sql3parse_table.c index ac5e276..a55badd 100644 --- a/sql3parse_table.c +++ b/sql3parse_table.c @@ -53,24 +53,36 @@ struct sql3foreignkey { sql3fk_deftype deferrable; }; +typedef struct { + sql3string name; // check constraint's name (can be NULL) + sql3string expr; // check constraint's expression +} sql3checkconstraint; + struct sql3column { sql3string name; // column name sql3string type; // column type (can be NULL) sql3string length; // column length (can be NULL) - sql3string constraint_name; // constraint name (can be NULL) - sql3string comment; // column comment (can be NULL) + sql3string comment; // column comment (can be NULL) bool is_primarykey; // primary key flag bool is_autoincrement; // autoincrement flag (only if is_primarykey is true) bool is_notnull; // not null flag bool is_unique; // is unique flag + sql3string pk_constraint_name; // primary key constraint name (can be NULL) sql3order_clause pk_order; // primary key order sql3conflict_clause pk_conflictclause; // primary key conflit clause + sql3string notnull_constraint_name; // not null constraint name (can be NULL) sql3conflict_clause notnull_conflictclause; // not null conflit clause + sql3string unique_constraint_name; // unique constraint name (can be NULL) sql3conflict_clause unique_conflictclause; // unique conflit clause - sql3string check_expr; // check expression (can be NULL) + size_t num_check_constraints; // number of check constraints + sql3checkconstraint *check_constraints; // array of check constraints (can be NULL) + sql3string default_constraint_name; // default constraint name (can be NULL) sql3string default_expr; // default expression (can be NULL) + sql3string collate_constraint_name; // collate constraint name (can be NULL) sql3string collate_name; // collate name (can be NULL) + sql3string foreignkey_constraint_name; // foreign key constraint name (can be NULL) sql3foreignkey *foreignkey_clause; // foreign key clause (can be NULL) + sql3string generated_constraint_name; // generated constraint name (can be NULL) sql3string generated_expr; // generated expression (can be NULL) sql3gen_type generated_type; // generated type }; @@ -913,17 +925,19 @@ static sql3error_code sql3parse_column_type (sql3state *state, sql3column *colum static sql3error_code sql3parse_column_constraints (sql3state *state, sql3column *column) { while (token_is_column_constraint(sql3lexer_peek(state))) { sql3token_t token = sql3lexer_next(state); - + // optional constraint name + sql3string constraint_name = {NULL, 0}; if (token == TOK_CONSTRAINT) { token = sql3lexer_next(state); if (token != TOK_IDENTIFIER) return SQL3ERROR_SYNTAX; - column->constraint_name = state->identifier; + constraint_name = state->identifier; token = sql3lexer_next(state); } switch (token) { - case TOK_PRIMARY: + case TOK_PRIMARY: { + column->pk_constraint_name = constraint_name; token = sql3lexer_next(state); if (token != TOK_KEY) return SQL3ERROR_SYNTAX; column->is_primarykey = true; @@ -933,50 +947,61 @@ static sql3error_code sql3parse_column_constraints (sql3state *state, sql3column sql3lexer_next(state); // consume TOK_AUTOINCREMENT column->is_autoincrement = true; } - break; - - case TOK_NOT: + } break; + + case TOK_NOT: { + column->notnull_constraint_name = constraint_name; token = sql3lexer_next(state); if (token != TOK_NULL) return SQL3ERROR_SYNTAX; column->is_notnull = true; if (sql3parse_optionalconflitclause(state, &column->notnull_conflictclause) != SQL3ERROR_NONE) return SQL3ERROR_SYNTAX; - break; - - case TOK_UNIQUE: + } break; + + case TOK_UNIQUE: { + column->unique_constraint_name = constraint_name; column->is_unique = true; if (sql3parse_optionalconflitclause(state, &column->unique_conflictclause) != SQL3ERROR_NONE) return SQL3ERROR_SYNTAX; - break; - - case TOK_CHECK: - column->check_expr = sql3parse_expression(state); - break; - - case TOK_DEFAULT: - // signed-number (+/-) => numeric-literal - // literal-value - // numeric-literal - // string-literal - // blob-literal (x/X'') - // NULL - // TRUE - // FALSE - // CURRENT_TIME - // CURRENT_DATE - // CURRENT_TIMESTAMP - // '(' expression ')' - + } break; + + case TOK_CHECK: { + // add check constraint to check constraints array + ++column->num_check_constraints; + column->check_constraints = SQL3REALLOC(column->check_constraints, sizeof(sql3checkconstraint) * column->num_check_constraints); + if (!column->check_constraints) return SQL3ERROR_MEMORY; + sql3checkconstraint *ptr = &column->check_constraints[column->num_check_constraints-1]; + ptr->name = constraint_name; + ptr->expr = sql3parse_expression(state); + } break; + + case TOK_DEFAULT: { + // signed-number (+/-) => numeric-literal + // literal-value + // numeric-literal + // string-literal + // blob-literal (x/X'') + // NULL + // TRUE + // FALSE + // CURRENT_TIME + // CURRENT_DATE + // CURRENT_TIMESTAMP + // '(' expression ')' + + column->default_constraint_name = constraint_name; // expressions are extracted (not fully parsed) in this version if (sql3lexer_peek(state) == TOK_OPEN_PARENTHESIS) column->default_expr = sql3parse_expression(state); else column->default_expr = sql3parse_literal(state); - break; - - case TOK_COLLATE: + } break; + + case TOK_COLLATE: { + column->collate_constraint_name = constraint_name; token = sql3lexer_next(state); if (token != TOK_IDENTIFIER) return SQL3ERROR_SYNTAX; column->collate_name = state->identifier; - break; - + } break; + case TOK_REFERENCES: { + column->foreignkey_constraint_name = constraint_name; sql3foreignkey *fk = sql3parse_foreignkey_clause(state); if (!fk) return SQL3ERROR_SYNTAX; column->foreignkey_clause = fk; @@ -984,6 +1009,7 @@ static sql3error_code sql3parse_column_constraints (sql3state *state, sql3column case TOK_GENERATED: case TOK_AS: { + column->generated_constraint_name = constraint_name; if (token == TOK_GENERATED) { token = sql3lexer_next(state); // 'column-constraint' syntax indicates that TOK_ALWAYS is @@ -998,12 +1024,12 @@ static sql3error_code sql3parse_column_constraints (sql3state *state, sql3column column->generated_expr = sql3parse_expression(state); if (sql3parse_optionalgentype(state, &column->generated_type) != SQL3ERROR_NONE) return SQL3ERROR_SYNTAX; } break; - + default: - return SQL3ERROR_SYNTAX; + return SQL3ERROR_SYNTAX; } } - + return SQL3ERROR_NONE; } @@ -1395,6 +1421,9 @@ void sql3table_free (sql3table *table) { // free columns for (size_t i=0; inum_columns; ++i) { sql3column *column = table->columns[i]; + if (column->check_constraints) { + SQL3FREE(column->check_constraints); + } if (column->foreignkey_clause) { if (column->foreignkey_clause->column_name) SQL3FREE(column->foreignkey_clause->column_name); SQL3FREE(column->foreignkey_clause); @@ -1494,11 +1523,6 @@ sql3string *sql3column_length (sql3column *column) { return &column->length; } -sql3string *sql3column_constraint_name (sql3column *column) { - CHECK_STR(column->constraint_name); - return &column->constraint_name; -} - sql3string *sql3column_comment (sql3column *column) { CHECK_STR(column->comment); return &column->comment; @@ -1520,6 +1544,11 @@ bool sql3column_is_unique (sql3column *column) { return column->is_unique; } +sql3string *sql3column_pk_constraint_name (sql3column *column) { + CHECK_STR(column->pk_constraint_name); + return &column->pk_constraint_name; +} + sql3order_clause sql3column_pk_order (sql3column *column) { return column->pk_order; } @@ -1528,17 +1557,44 @@ sql3conflict_clause sql3column_pk_conflictclause (sql3column *column) { return column->pk_conflictclause; } +sql3string *sql3column_notnull_constraint_name (sql3column *column) { + CHECK_STR(column->notnull_constraint_name); + return &column->notnull_constraint_name; +} + sql3conflict_clause sql3column_notnull_conflictclause (sql3column *column) { return column->notnull_conflictclause; } +sql3string *sql3column_unique_constraint_name (sql3column *column) { + CHECK_STR(column->unique_constraint_name); + return &column->unique_constraint_name; +} + sql3conflict_clause sql3column_unique_conflictclause (sql3column *column) { return column->unique_conflictclause; } -sql3string *sql3column_check_expr (sql3column *column) { - CHECK_STR(column->check_expr); - return &column->check_expr; +size_t sql3column_num_check_constraints (sql3column *column) +{ + return column->num_check_constraints; +} + +sql3string *sql3column_check_constraint_name (sql3column *column, size_t index) { + CHECK_IDX(index, column->num_check_constraints); + CHECK_STR(column->check_constraints[index].name); + return &(column->check_constraints[index].name); +} + +sql3string *sql3column_check_expr (sql3column *column, size_t index) { + CHECK_IDX(index, column->num_check_constraints); + CHECK_STR(column->check_constraints[index].expr); + return &(column->check_constraints[index].expr); +} + +sql3string *sql3column_default_constraint_name (sql3column *column) { + CHECK_STR(column->default_constraint_name); + return &column->default_constraint_name; } sql3string *sql3column_default_expr (sql3column *column) { @@ -1546,21 +1602,36 @@ sql3string *sql3column_default_expr (sql3column *column) { return &column->default_expr; } +sql3string *sql3column_collate_constraint_name (sql3column *column) { + CHECK_STR(column->collate_constraint_name); + return &column->collate_constraint_name; +} + sql3string *sql3column_collate_name (sql3column *column) { CHECK_STR(column->collate_name); return &column->collate_name; } +sql3string *sql3column_foreignkey_constraint_name (sql3column *column) { + CHECK_STR(column->foreignkey_constraint_name); + return &column->foreignkey_constraint_name; +} + sql3foreignkey *sql3column_foreignkey_clause (sql3column *column) { return column->foreignkey_clause; } -sql3string* sql3column_generated_expr (sql3column* column) { +sql3string *sql3column_generated_constraint_name (sql3column *column) { + CHECK_STR(column->generated_constraint_name); + return &column->generated_constraint_name; +} + +sql3string *sql3column_generated_expr (sql3column *column) { CHECK_STR(column->generated_expr); return &column->generated_expr; } -sql3gen_type sql3column_generated_type (sql3column* column) { +sql3gen_type sql3column_generated_type (sql3column *column) { return column->generated_type; } diff --git a/sql3parse_table.h b/sql3parse_table.h index 5dfcb1a..4b036e1 100644 --- a/sql3parse_table.h +++ b/sql3parse_table.h @@ -140,22 +140,30 @@ sql3foreignkey *sql3table_constraint_foreignkey_clause (sql3tableconstraint *tco sql3string *sql3column_name (sql3column *column); sql3string *sql3column_type (sql3column *column); sql3string *sql3column_length (sql3column *column); -sql3string *sql3column_constraint_name (sql3column *column); sql3string *sql3column_comment (sql3column *column); bool sql3column_is_primarykey (sql3column *column); bool sql3column_is_autoincrement (sql3column *column); bool sql3column_is_notnull (sql3column *column); bool sql3column_is_unique (sql3column *column); +sql3string *sql3column_pk_constraint_name (sql3column *column); sql3order_clause sql3column_pk_order (sql3column *column); sql3conflict_clause sql3column_pk_conflictclause (sql3column *column); +sql3string *sql3column_notnull_constraint_name (sql3column *column); sql3conflict_clause sql3column_notnull_conflictclause (sql3column *column); +sql3string *sql3column_unique_constraint_name (sql3column *column); sql3conflict_clause sql3column_unique_conflictclause (sql3column *column); -sql3string *sql3column_check_expr (sql3column *column); +size_t sql3column_num_check_constraints (sql3column *column); +sql3string *sql3column_check_constraint_name (sql3column *column, size_t index); +sql3string *sql3column_check_expr (sql3column *column, size_t index); +sql3string *sql3column_default_constraint_name (sql3column *column); sql3string *sql3column_default_expr (sql3column *column); +sql3string *sql3column_collate_constraint_name (sql3column *column); sql3string *sql3column_collate_name (sql3column *column); +sql3string *sql3column_foreignkey_constraint_name (sql3column *column); sql3foreignkey *sql3column_foreignkey_clause (sql3column *column); -sql3string* sql3column_generated_expr (sql3column* column); -sql3gen_type sql3column_generated_type (sql3column* column); +sql3string *sql3column_generated_constraint_name (sql3column *column); +sql3string *sql3column_generated_expr (sql3column *column); +sql3gen_type sql3column_generated_type (sql3column *column); // Foreign Key sql3string *sql3foreignkey_table (sql3foreignkey *fk);