From edcd07a23c0f824ce121e86dd6a11806ea0d6a90 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:23:54 +0000 Subject: [PATCH 1/3] Remove quoted string support from default modifier; use raw SQL values Agent-Logs-Url: https://github.com/headercat/erdn-lang/sessions/0427966f-a4d2-485a-81b6-64f12d226b10 Co-authored-by: nemorize <51209191+nemorize@users.noreply.github.com> --- internal/parser/parser.go | 2 -- internal/parser/parser_test.go | 2 +- internal/render/render_test.go | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/parser/parser.go b/internal/parser/parser.go index c72642f..75e6279 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -228,8 +228,6 @@ func (p *parser) parseModifier() (ast.Modifier, error) { val := p.advance() var valStr string switch val.Type { - case lexer.TOKEN_STRING: - valStr = val.Value case lexer.TOKEN_NUMBER: valStr = val.Value case lexer.TOKEN_IDENT: diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index 31adc58..38dd030 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -63,7 +63,7 @@ func TestParseColumnTypeParams(t *testing.T) { func TestParseDefaultModifier(t *testing.T) { src := `table t ( - title varchar(255) default("Untitled") + title varchar(255) default(Untitled) )` prog, err := ParseString(src) if err != nil { diff --git a/internal/render/render_test.go b/internal/render/render_test.go index 95ab40e..9cd8126 100644 --- a/internal/render/render_test.go +++ b/internal/render/render_test.go @@ -53,7 +53,7 @@ table t (id bigint)`) func TestSVGModifiers(t *testing.T) { svg := generateSVG(t, `table t ( id bigint primary-key auto-increment - name varchar(255) not-null default("hi") + name varchar(255) not-null default(hi) )`) if !strings.Contains(svg, "PK") { t.Error("expected PK modifier in SVG") From fae4fb8074786b56468ab9343dd25524013779d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:29:04 +0000 Subject: [PATCH 2/3] Re-add quoted string support to default modifier; store value with quotes Agent-Logs-Url: https://github.com/headercat/erdn-lang/sessions/c08688ca-bb4b-4840-99fe-0e31796505b3 Co-authored-by: nemorize <51209191+nemorize@users.noreply.github.com> --- internal/parser/parser.go | 2 ++ internal/parser/parser_test.go | 6 +++--- internal/render/render_test.go | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 75e6279..46cf134 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -228,6 +228,8 @@ func (p *parser) parseModifier() (ast.Modifier, error) { val := p.advance() var valStr string switch val.Type { + case lexer.TOKEN_STRING: + valStr = `"` + val.Value + `"` case lexer.TOKEN_NUMBER: valStr = val.Value case lexer.TOKEN_IDENT: diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index 38dd030..e7fa430 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -63,7 +63,7 @@ func TestParseColumnTypeParams(t *testing.T) { func TestParseDefaultModifier(t *testing.T) { src := `table t ( - title varchar(255) default(Untitled) + title varchar(255) default("Untitled") )` prog, err := ParseString(src) if err != nil { @@ -73,8 +73,8 @@ 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) } } diff --git a/internal/render/render_test.go b/internal/render/render_test.go index 9cd8126..95ab40e 100644 --- a/internal/render/render_test.go +++ b/internal/render/render_test.go @@ -53,7 +53,7 @@ table t (id bigint)`) func TestSVGModifiers(t *testing.T) { svg := generateSVG(t, `table t ( id bigint primary-key auto-increment - name varchar(255) not-null default(hi) + name varchar(255) not-null default("hi") )`) if !strings.Contains(svg, "PK") { t.Error("expected PK modifier in SVG") From d2ff05d6e497a33170bd02110bf7303a85bd8c68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:32:09 +0000 Subject: [PATCH 3/3] Support default(NOW()) function-call syntax; update examples, docs, tests Agent-Logs-Url: https://github.com/headercat/erdn-lang/sessions/3e1bf0e9-b31e-4020-b99f-e8242a120fcc Co-authored-by: nemorize <51209191+nemorize@users.noreply.github.com> --- README.md | 6 +++--- examples/advanced.erdn | 8 ++++---- examples/blog.erdn | 16 ++++++++-------- examples/cjk.erdn | 20 ++++++++++---------- examples/ecommerce.erdn | 20 ++++++++++---------- examples/simple.erdn | 4 ++-- internal/parser/parser.go | 11 ++++++++++- internal/parser/parser_test.go | 17 +++++++++++++++++ website/guide.md | 2 +- website/syntax.md | 6 +++--- 10 files changed, 68 insertions(+), 42 deletions(-) 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 46cf134..c444a9a 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -233,7 +233,16 @@ func (p *parser) parseModifier() (ast.Modifier, error) { 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 e7fa430..59a61c5 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -78,6 +78,23 @@ func TestParseDefaultModifier(t *testing.T) { } } +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) + } +} + func TestParseLink(t *testing.T) { src := `table a (id bigint) table b (a_id bigint) 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