diff --git a/README.md b/README.md index 1715cbf..1e986ca 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ table users ( id bigint primary-key auto-increment username varchar(64) not-null indexed bio text nullable -created_at timestamp not-null default("NOW()") +created_at timestamp not-null default(NOW()) ``` A `#` comment on the line immediately above a column is rendered as the column's annotation in the diagram. @@ -141,7 +141,7 @@ A `#` comment on the line immediately above a column is rendered as the column's | `not-null` | Column must not contain NULL values. | | `nullable` | Column explicitly allows NULL values. | | `indexed` | Column has a database index. | -| `default("")` | Specifies a default value expression. | +| `default()` | Specifies a default value expression. | > `nullable` and `not-null` are mutually exclusive on the same column. @@ -188,7 +188,7 @@ table posts ( body text not-null # draft, published, archived status varchar(32) not-null default("draft") - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) # An author can write many posts diff --git a/examples/advanced.erdn b/examples/advanced.erdn index 030b782..022c653 100644 --- a/examples/advanced.erdn +++ b/examples/advanced.erdn @@ -45,8 +45,8 @@ table tasks ( # blocked – waiting on an external dependency # cancelled – will not be completed status varchar(32) not-null default("pending") - created_at timestamp not-null default("NOW()") - updated_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) + updated_at timestamp not-null default(NOW()) ) // ── Relationship 3: between two tables declared later ──────── @@ -72,7 +72,7 @@ table projects ( description text nullable # Lifecycle state: active / archived / deleted status varchar(32) not-null default("active") - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) // ── users table – declared last ────────────────────────────── @@ -90,5 +90,5 @@ table users ( password_hash varchar(255) not-null # Account state: active / suspended / deleted status varchar(16) not-null default("active") - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) diff --git a/examples/blog.erdn b/examples/blog.erdn index d0708cf..bd0500c 100644 --- a/examples/blog.erdn +++ b/examples/blog.erdn @@ -18,8 +18,8 @@ table authors ( avatar_url varchar(1024) nullable # admin, editor, contributor, subscriber role varchar(32) not-null default("contributor") - created_at timestamp not-null default("NOW()") - updated_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) + updated_at timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -61,8 +61,8 @@ table posts ( status varchar(32) not-null default("draft") # NULL until first published published_at timestamp nullable - created_at timestamp not-null default("NOW()") - updated_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) + updated_at timestamp not-null default(NOW()) ) table post_tags ( @@ -86,8 +86,8 @@ table comments ( body text not-null # pending, approved, spam, deleted status varchar(32) not-null default("pending") - created_at timestamp not-null default("NOW()") - updated_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) + updated_at timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -108,7 +108,7 @@ table media ( size_bytes bigint not-null # Descriptive alt text for accessibility alt_text varchar(512) nullable - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -123,7 +123,7 @@ table subscriptions ( author_id bigint nullable indexed # active, unsubscribed, bounced status varchar(32) not-null default("active") - subscribed_at timestamp not-null default("NOW()") + subscribed_at timestamp not-null default(NOW()) unsubscribed_at timestamp nullable ) diff --git a/examples/cjk.erdn b/examples/cjk.erdn index 78f7c7f..fcde974 100644 --- a/examples/cjk.erdn +++ b/examples/cjk.erdn @@ -18,8 +18,8 @@ table 用户 ( 密码哈希 varchar(255) not-null # 账号状态:active-活跃 / suspended-暂停 / deleted-已删除 状態 varchar(16) not-null default("active") - 注册时间 timestamp not-null default("NOW()") - 更新时间 timestamp not-null default("NOW()") + 注册时间 timestamp not-null default(NOW()) + 更新时间 timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -40,7 +40,7 @@ table 文章 ( # 发布状态:draft-草稿 / published-已发布 / archived-已归档 状态 varchar(32) not-null default("draft") 发布时间 timestamp nullable - 创建时间 timestamp not-null default("NOW()") + 创建时间 timestamp not-null default(NOW()) ) # 评论表 @@ -54,7 +54,7 @@ table 评论 ( # NULL 表示顶层评论;非 NULL 则指向父评论 父评论ID bigint nullable indexed 评论内容 text not-null - 创建时间 timestamp not-null default("NOW()") + 创建时间 timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -73,8 +73,8 @@ table 記事 ( 本文 text not-null # 公開ステータス: draft / published / archived ステータス varchar(32) not-null default("draft") - 作成日時 timestamp not-null default("NOW()") - 更新日時 timestamp not-null default("NOW()") + 作成日時 timestamp not-null default(NOW()) + 更新日時 timestamp not-null default(NOW()) ) # コメントテーブル @@ -88,7 +88,7 @@ table コメント ( # NULL なら最上位コメント、非 NULL なら返信コメント 親コメントID bigint nullable indexed 本文 text not-null - 作成日時 timestamp not-null default("NOW()") + 作成日時 timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -107,8 +107,8 @@ table 게시물 ( 본문 text not-null # 상태: draft / published / archived 상태 varchar(32) not-null default("draft") - 생성일시 timestamp not-null default("NOW()") - 수정일시 timestamp not-null default("NOW()") + 생성일시 timestamp not-null default(NOW()) + 수정일시 timestamp not-null default(NOW()) ) # 댓글 테이블 @@ -123,7 +123,7 @@ table 댓글 ( # NULL이면 최상위 댓글, 아니면 대댓글 부모댓글ID bigint nullable indexed 내용 text not-null - 생성일시 timestamp not-null default("NOW()") + 생성일시 timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- diff --git a/examples/ecommerce.erdn b/examples/ecommerce.erdn index 9147639..800c3e7 100644 --- a/examples/ecommerce.erdn +++ b/examples/ecommerce.erdn @@ -17,8 +17,8 @@ table users ( # Account state: active, suspended, deleted status varchar(16) not-null default("active") email_verified_at timestamp nullable - created_at timestamp not-null default("NOW()") - updated_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) + updated_at timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -36,7 +36,7 @@ table addresses ( postal_code varchar(32) not-null country_code char(2) not-null default("US") is_default bool not-null default("false") - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -67,8 +67,8 @@ table products ( # Current stock on hand; goes negative when oversold stock_qty int not-null default("0") is_active bool not-null default("true") - created_at timestamp not-null default("NOW()") - updated_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) + updated_at timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -86,8 +86,8 @@ table orders ( total_cents int not-null currency char(3) not-null default("USD") notes text nullable - placed_at timestamp not-null default("NOW()") - updated_at timestamp not-null default("NOW()") + placed_at timestamp not-null default(NOW()) + updated_at timestamp not-null default(NOW()) ) table order_items ( @@ -118,7 +118,7 @@ table payments ( # stripe, paypal, braintree, etc. provider varchar(64) not-null paid_at timestamp nullable - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -135,7 +135,7 @@ table reviews ( headline varchar(255) nullable body text nullable # Prevent duplicate reviews by same user on same product - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- @@ -147,7 +147,7 @@ table wishlists ( id bigint primary-key auto-increment user_id bigint not-null indexed product_id bigint not-null indexed - added_at timestamp not-null default("NOW()") + added_at timestamp not-null default(NOW()) ) // --------------------------------------------------------------------------- diff --git a/examples/simple.erdn b/examples/simple.erdn index cfb08c0..c6cd490 100644 --- a/examples/simple.erdn +++ b/examples/simple.erdn @@ -6,7 +6,7 @@ table users ( username varchar(255) indexed not-null email varchar(255) indexed not-null bio text nullable - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) table posts ( @@ -15,7 +15,7 @@ table posts ( title varchar(255) not-null body text nullable author_id bigint not-null indexed - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) # User has many posts diff --git a/internal/parser/parser.go b/internal/parser/parser.go index c72642f..c444a9a 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -229,11 +229,20 @@ func (p *parser) parseModifier() (ast.Modifier, error) { var valStr string switch val.Type { case lexer.TOKEN_STRING: - valStr = val.Value + valStr = `"` + val.Value + `"` case lexer.TOKEN_NUMBER: valStr = val.Value case lexer.TOKEN_IDENT: - valStr = val.Value + // Check for function-call syntax: IDENT() + if p.peek().Type == lexer.TOKEN_LPAREN { + p.advance() // consume ( + if _, err := p.expect(lexer.TOKEN_RPAREN); err != nil { + return ast.Modifier{}, err + } + valStr = val.Value + "()" + } else { + valStr = val.Value + } default: return ast.Modifier{}, fmt.Errorf("line %d col %d: expected default value, got %q", val.Line, val.Col, val.Value) } diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index 31adc58..59a61c5 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -73,8 +73,25 @@ func TestParseDefaultModifier(t *testing.T) { if len(col.Modifiers) != 1 || col.Modifiers[0].Kind != ast.ModDefault { t.Errorf("expected default modifier") } - if col.Modifiers[0].Value != "Untitled" { - t.Errorf("expected default value 'Untitled', got %q", col.Modifiers[0].Value) + if col.Modifiers[0].Value != `"Untitled"` { + t.Errorf(`expected default value '"Untitled"', got %q`, col.Modifiers[0].Value) + } +} + +func TestParseDefaultFunctionCall(t *testing.T) { + src := `table t ( + created_at timestamp default(NOW()) +)` + prog, err := ParseString(src) + if err != nil { + t.Fatalf("parse error: %v", err) + } + col := prog.Tables[0].Columns[0] + if len(col.Modifiers) != 1 || col.Modifiers[0].Kind != ast.ModDefault { + t.Errorf("expected default modifier") + } + if col.Modifiers[0].Value != "NOW()" { + t.Errorf("expected default value 'NOW()', got %q", col.Modifiers[0].Value) } } diff --git a/website/guide.md b/website/guide.md index 5d23dc7..083351f 100644 --- a/website/guide.md +++ b/website/guide.md @@ -88,7 +88,7 @@ table posts ( body text not-null # draft, published, archived status varchar(32) not-null default("draft") - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) # An author can write many posts diff --git a/website/syntax.md b/website/syntax.md index 17884e0..641e078 100644 --- a/website/syntax.md +++ b/website/syntax.md @@ -144,13 +144,13 @@ Zero or more modifiers may follow the type (and type parameters) on the same lin The `default` modifier requires a parenthesized value: ``` -default("NOW()") +default(NOW()) default("draft") default(0) default(active) ``` -The value inside parentheses may be a **string literal** (double-quoted), a **number**, or an **identifier**. +The value inside parentheses may be a **string literal** (double-quoted), a **number**, an **identifier**, or a **function call** (e.g. `NOW()`). ### Modifier Ordering @@ -275,7 +275,7 @@ table posts ( body text not-null # draft, published, archived status varchar(32) not-null default("draft") - created_at timestamp not-null default("NOW()") + created_at timestamp not-null default(NOW()) ) # An author can write many posts