diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index d291ced933..a3caa6955f 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -51,6 +51,10 @@ - Limit wildcard edge connections and selectors to bounded expansion work, returning a clear error before excessive fanout consumes resources. [#2926](https://github.com/d2lang/d2/pull/2926) + - Keep `sql_table` column types that match the column name, such as + `date: date`, instead of rendering an empty type. [#2950](https://github.com/d2lang/d2/pull/2950) + - Keep `class` field types and method return types that match the name, such + as `date: date`, instead of dropping them. [#2952](https://github.com/d2lang/d2/pull/2952) - decoding and assets: - Cap decompressed URL-encoded D2 input at 16 MiB. [#2902](https://github.com/d2lang/d2/pull/2902) - Bound image references, locators, fetched and decoded bytes, cached data, and diff --git a/d2compiler/compile.go b/d2compiler/compile.go index a871692b87..56050ee478 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -421,6 +421,7 @@ type compiler struct { err *d2parser.ParseError activeClassMaps map[*d2ir.Map]struct{} classMapsByRoot map[*d2ir.Map]map[string]*d2ir.Map + explicitLabels map[*d2graph.Attributes]struct{} ctx context.Context fatalErr error } @@ -793,6 +794,15 @@ func (c *compiler) compileLabel(attrs *d2graph.Attributes, f d2ir.Node) { attrs.Label.Value = scalar.ScalarString() } attrs.Label.MapKey = f.LastPrimaryKey() + if c.explicitLabels == nil { + c.explicitLabels = make(map[*d2graph.Attributes]struct{}) + } + c.explicitLabels[attrs] = struct{}{} +} + +func (c *compiler) hasExplicitLabel(attrs *d2graph.Attributes) bool { + _, ok := c.explicitLabels[attrs] + return ok } func (c *compiler) compilePosition(attrs *d2graph.Attributes, f *d2ir.Field) { @@ -1407,9 +1417,10 @@ func (c *compiler) compileClass(obj *d2graph.Object) { } } + // A field or method without an explicit label defaults it to its ID, which is not a type. if !strings.Contains(f.IDVal, "(") { typ := f.Label.Value - if typ == f.IDVal { + if !c.hasExplicitLabel(&f.Attributes) && typ == f.IDVal { typ = "" } underline := f.Attributes.Style.Underline != nil && f.Attributes.Style.Underline.Value == "true" @@ -1420,10 +1431,8 @@ func (c *compiler) compileClass(obj *d2graph.Object) { Underline: underline, }) } else { - // TODO: Not great, AST should easily allow specifying alternate primary field - // as an explicit label should change the name. returnType := f.Label.Value - if returnType == f.IDVal { + if !c.hasExplicitLabel(&f.Attributes) && returnType == f.IDVal { returnType = "void" } underline := f.Attributes.Style.Underline != nil && f.Attributes.Style.Underline.Value == "true" @@ -1464,9 +1473,8 @@ func (c *compiler) compileSQLTable(obj *d2graph.Object) { return } typ := col.Label.Value - if typ == col.IDVal { - // Not great, AST should easily allow specifying alternate primary field - // as an explicit label should change the name. + // A column without an explicit label defaults it to its ID, which is not a type. + if !c.hasExplicitLabel(&col.Attributes) && typ == col.IDVal { typ = "" } d2Col := d2target.SQLColumn{ diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 0be7759ced..03ce69af99 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -2857,6 +2857,47 @@ x.y -> a.b: { assert.String(t, `Is()`, g.Objects[0].SQLTable.Columns[1].Name.Label) }, }, + { + name: "sql_column_type_matches_name", + + text: `time: { + shape: sql_table + id + created {constraint: primary_key} + date: date + key: key + key {constraint: unique} +} +`, + assertions: func(t *testing.T, g *d2graph.Graph) { + cols := g.Objects[0].SQLTable.Columns + assert.String(t, ``, cols[0].Type.Label) + assert.String(t, ``, cols[1].Type.Label) + assert.String(t, `date`, cols[2].Type.Label) + assert.String(t, `key`, cols[3].Type.Label) + }, + }, + { + name: "class_field_type_matches_name", + + text: `Event: { + shape: class + id + date: date + +status: status + save() + now(): now() +} +`, + assertions: func(t *testing.T, g *d2graph.Graph) { + class := g.Objects[0].Class + assert.String(t, ``, class.Fields[0].Type) + assert.String(t, `date`, class.Fields[1].Type) + assert.String(t, `status`, class.Fields[2].Type) + assert.String(t, `void`, class.Methods[0].Return) + assert.String(t, `now()`, class.Methods[1].Return) + }, + }, { name: "nested_sql", diff --git a/testdata/d2compiler/TestCompile/class_field_type_matches_name.exp.json b/testdata/d2compiler/TestCompile/class_field_type_matches_name.exp.json new file mode 100644 index 0000000000..cefa08378d --- /dev/null +++ b/testdata/d2compiler/TestCompile/class_field_type_matches_name.exp.json @@ -0,0 +1,328 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,0:0:0-8:0:86", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,0:0:0-7:1:85", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,0:0:0-0:5:5", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,0:0:0-0:5:5", + "value": [ + { + "string": "Event", + "raw_string": "Event" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,0:7:7-7:1:85", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,1:2:11-1:14:23", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,1:2:11-1:7:16", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,1:2:11-1:7:16", + "value": [ + { + "string": "shape", + "raw_string": "shape" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,1:9:18-1:14:23", + "value": [ + { + "string": "class", + "raw_string": "class" + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,2:2:26-2:4:28", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,2:2:26-2:4:28", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,2:2:26-2:4:28", + "value": [ + { + "string": "id", + "raw_string": "id" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,3:2:31-3:12:41", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,3:2:31-3:6:35", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,3:2:31-3:6:35", + "value": [ + { + "string": "date", + "raw_string": "date" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,3:8:37-3:12:41", + "value": [ + { + "string": "date", + "raw_string": "date" + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,4:2:44-4:17:59", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,4:2:44-4:9:51", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,4:2:44-4:9:51", + "value": [ + { + "string": "+status", + "raw_string": "+status" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,4:11:53-4:17:59", + "value": [ + { + "string": "status", + "raw_string": "status" + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,5:2:62-5:8:68", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,5:2:62-5:8:68", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,5:2:62-5:8:68", + "value": [ + { + "string": "save()", + "raw_string": "save()" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,6:2:71-6:14:83", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,6:2:71-6:7:76", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,6:2:71-6:7:76", + "value": [ + { + "string": "now()", + "raw_string": "now()" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,6:9:78-6:14:83", + "value": [ + { + "string": "now()", + "raw_string": "now()" + } + ] + } + } + } + } + ] + } + } + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "iconStyle": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "Event", + "id_val": "Event", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,0:0:0-0:5:5", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/class_field_type_matches_name.d2,0:0:0-0:5:5", + "value": [ + { + "string": "Event", + "raw_string": "Event" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "class": { + "fields": [ + { + "name": "id", + "type": "", + "visibility": "public", + "underline": false + }, + { + "name": "date", + "type": "date", + "visibility": "public", + "underline": false + }, + { + "name": "status", + "type": "status", + "visibility": "public", + "underline": false + } + ], + "methods": [ + { + "name": "save()", + "return": "void", + "visibility": "public", + "underline": false + }, + { + "name": "now()", + "return": "now()", + "visibility": "public", + "underline": false + } + ] + }, + "attributes": { + "label": { + "value": "Event" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "iconStyle": {}, + "near_key": null, + "shape": { + "value": "class" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + }, + "err": null +} diff --git a/testdata/d2compiler/TestCompile/sql_column_type_matches_name.exp.json b/testdata/d2compiler/TestCompile/sql_column_type_matches_name.exp.json new file mode 100644 index 0000000000..68b59206c8 --- /dev/null +++ b/testdata/d2compiler/TestCompile/sql_column_type_matches_name.exp.json @@ -0,0 +1,480 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,0:0:0-8:0:121", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,0:0:0-7:1:120", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,0:0:0-0:4:4", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,0:0:0-0:4:4", + "value": [ + { + "string": "time", + "raw_string": "time" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,0:6:6-7:1:120", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,1:2:10-1:18:26", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,1:2:10-1:7:15", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,1:2:10-1:7:15", + "value": [ + { + "string": "shape", + "raw_string": "shape" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,1:9:17-1:18:26", + "value": [ + { + "string": "sql_table", + "raw_string": "sql_table" + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,2:2:29-2:4:31", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,2:2:29-2:4:31", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,2:2:29-2:4:31", + "value": [ + { + "string": "id", + "raw_string": "id" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,3:2:34-3:35:67", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,3:2:34-3:9:41", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,3:2:34-3:9:41", + "value": [ + { + "string": "created", + "raw_string": "created" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,3:10:42-3:35:67", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,3:11:43-3:34:66", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,3:11:43-3:21:53", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,3:11:43-3:21:53", + "value": [ + { + "string": "constraint", + "raw_string": "constraint" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,3:23:55-3:34:66", + "value": [ + { + "string": "primary_key", + "raw_string": "primary_key" + } + ] + } + } + } + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,4:2:70-4:12:80", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,4:2:70-4:6:74", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,4:2:70-4:6:74", + "value": [ + { + "string": "date", + "raw_string": "date" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,4:8:76-4:12:80", + "value": [ + { + "string": "date", + "raw_string": "date" + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,5:2:83-5:10:91", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,5:2:83-5:5:86", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,5:2:83-5:5:86", + "value": [ + { + "string": "key", + "raw_string": "key" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,5:7:88-5:10:91", + "value": [ + { + "string": "key", + "raw_string": "key" + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,6:2:94-6:26:118", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,6:2:94-6:5:97", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,6:2:94-6:5:97", + "value": [ + { + "string": "key", + "raw_string": "key" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,6:6:98-6:26:118", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,6:7:99-6:25:117", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,6:7:99-6:17:109", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,6:7:99-6:17:109", + "value": [ + { + "string": "constraint", + "raw_string": "constraint" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,6:19:111-6:25:117", + "value": [ + { + "string": "unique", + "raw_string": "unique" + } + ] + } + } + } + } + ] + } + } + } + } + ] + } + } + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "iconStyle": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "time", + "id_val": "time", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,0:0:0-0:4:4", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/sql_column_type_matches_name.d2,0:0:0-0:4:4", + "value": [ + { + "string": "time", + "raw_string": "time" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "sql_table": { + "columns": [ + { + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "type": { + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "constraint": null, + "reference": "" + }, + { + "name": { + "label": "created", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "type": { + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "constraint": [ + "primary_key" + ], + "reference": "" + }, + { + "name": { + "label": "date", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "type": { + "label": "date", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "constraint": null, + "reference": "" + }, + { + "name": { + "label": "key", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "type": { + "label": "key", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "constraint": [ + "unique" + ], + "reference": "" + } + ] + }, + "attributes": { + "label": { + "value": "time" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "iconStyle": {}, + "near_key": null, + "shape": { + "value": "sql_table" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + }, + "err": null +}