{"id":8090,"date":"2026-04-03T15:21:21","date_gmt":"2026-04-03T07:21:21","guid":{"rendered":"https:\/\/kyle.ai\/blog\/?p=8090"},"modified":"2026-04-03T15:39:07","modified_gmt":"2026-04-03T07:39:07","slug":"%e8%87%aa%e5%b7%b1%e5%88%9b%e9%80%a0%e7%9a%84-tml%ef%bc%9atest-markup-language","status":"publish","type":"post","link":"https:\/\/kyle.ai\/blog\/8090.html","title":{"rendered":"\u81ea\u5df1\u521b\u9020\u7684 TML\uff1aTest Markup Language"},"content":{"rendered":"\n<p>\u6211\u4e4b\u524d\u5728\u9879\u76ee\u5f00\u53d1\u4e2d\u9047\u5230\u4e00\u4e2a\u75db\u70b9\uff0c\u6bd4\u5982\u6211\u5728\u5b9e\u73b0 SQL \u89e3\u6790\u7684\u82e5\u5e72\u529f\u80fd\uff0c\u9700\u8981\u5199\u5927\u91cf\u7684\u5355\u5143\u6d4b\u8bd5\uff0c\u6d4b\u8bd5\u4e2d\u9700\u8981\u6307\u5b9a SQL \u5b57\u7b26\u4e32\uff0c\u672c\u7740\u4ee3\u7801\u4e0e\u6570\u636e\u5206\u79bb\u7684\u601d\u60f3\uff0c\u8fd9\u4e9b\u5355\u6d4b\u7684 SQL \u548c\u5176\u5b83\u76f8\u5173\u7684\u53d8\u91cf\u6570\u636e\uff0c\u5e94\u8be5\u5355\u72ec\u5b58\u50a8\u5728\u4ee3\u7801\u5916\u7684\u67d0\u4e2a\u5730\u65b9\u3002<\/p>\n\n\n\n<p>\u5f00\u53d1\u4e2d\u5e38\u89c1\u7684\u4e00\u4e9b\u914d\u7f6e\u6587\u4ef6\u683c\u5f0f\uff0c\u6bd4\u5982 json\u3001yaml\u3001toml \u7b49\u7b49\uff0c\u90fd\u662f\u6709\u7279\u5b9a\u8bed\u6cd5\u89c4\u5219\uff0c\u800c SQL \u5b57\u7b26\u4e32\u4e2d\u5404\u79cd\u53ef\u80fd\u6709\u7684\u5b57\u7b26\u90fd\u6709\uff0c\u5bb9\u6613\u51fa\u73b0\u683c\u5f0f\u95ee\u9898\uff0c\u6bd4\u5982\u6362\u884c\u7b26\uff0c\u5355\u53cc\u5f15\u53f7\uff0c\u53ca\u5176\u5b83\u95ee\u9898\u3002\u7f16\u5199\u8fd9\u4e9b\u914d\u7f6e\u6587\u4ef6\u5c31\u663e\u5f97\u76f8\u5f53\u7e41\u7410\u800c\u4e14\u6613\u51fa\u9519\u3002<\/p>\n\n\n\n<p>\u4e8e\u662f\u6211\u5c31\u4ea7\u751f\u4e86\u4e00\u4e2a\u60f3\u6cd5\uff0c\u81ea\u5df1\u7f16\u5199\u4e00\u79cd\u7279\u6b8a\u7684\u914d\u7f6e\u6587\u4ef6\u683c\u5f0f\uff0c\u8ba9\u5199 SQL \u5b57\u7b26\u4e32\u53d8\u5f97\u76f4\u89c2\u3001\u7b80\u5355\u3002\u6211\u628a\u6211\u8bbe\u8ba1\u7684\u8fd9\u79cd\u7b80\u5355\u6587\u4ef6\u683c\u5f0f\u79f0\u4e4b\u4e3a\uff1aTML\uff0c\u5168\u79f0\u4e3a Test Markup Language\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">tml \u6807\u8bb0\u8bed\u8a00<\/h2>\n\n\n\n<p><code>tml<\/code> \u5168\u79f0 <code>Testing Markup Language<\/code>\uff0c\u662f\u7528\u4e8e\u5b58\u50a8\u5355\u5143\u6d4b\u8bd5\u4e2d\u53d8\u91cf\u7684\u6587\u672c\u6587\u4ef6\u3002\u683c\u5f0f\u7b80\u5355<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u7ed3\u6784\u4f53\u5b9a\u4e49<\/h2>\n\n\n\n<p>\u76ee\u524d\u4ec5\u652f\u6301\u4ee5\u4e0b\u51e0\u79cd\u5b57\u6bb5\u6570\u636e\u7c7b\u578b<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>int<\/li>\n\n\n\n<li>string<\/li>\n\n\n\n<li>[]string<\/li>\n\n\n\n<li>map[string]string<\/li>\n<\/ul>\n\n\n\n<p>\u4f7f\u7528 <code>tml<\/code> \u6765\u6807\u8bb0 struct \u4e2d\u7684\u5b57\u6bb5\u3002\u5982<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\ntype tmlData struct {\n\tSql  string            `tml:&quot;sql&quot;`\n\tDeps string            `tml:&quot;deps&quot;`\n\tMeta map&#x5B;string]string `tml:&quot;meta&quot;`\n}\n<\/pre><\/div>\n\n\n<p>\u6309\u4e0a\u9762\u5b9a\u4e49\uff0c\u9047\u5230 sql \u53d8\u91cf\uff0c\u4f1a\u6309\u5b57\u7b26\u4e32\u63d0\u53d6\u3002meta \u53d8\u91cf\u6309\u5b57\u5178\u89c4\u5219\u63d0\u53d6\u3002<\/p>\n\n\n\n<p>\u89e3\u6790\u793a\u4f8b<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\nr, err := os.Open(path)\nif err != nil {\n    t.Fatalf(&quot;open %s failed, err: %s&quot;, path, err)\n}\ndefer r.Close()\n\nd := tml.NewDecoder(r)\nvar data tmlData\nif err = d.Decode(&amp;data); err != nil {\n    t.Fatalf(&quot;parse tml file %s failed, err: %s&quot;, path, err)\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">tml \u6587\u4ef6\u8bed\u6cd5<\/h2>\n\n\n\n<h4 class=\"wp-block-heading\">\u53d8\u91cf\u8d4b\u503c<\/h4>\n\n\n\n<p>\u884c\u5f00\u59cb\uff0c\u8fde\u63a5\u4e24\u4e2a @ \uff0c\u518d\u63a5\u7740\u53d8\u91cf\u540d\uff0c@@ \u524d\u9762\u4e0d\u80fd\u6709\u7a7a\u767d\u5b57\u7b26<\/p>\n\n\n\n<p>\u4f8b\u5982<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n@@ sql\n select from\n    table aaa\n<\/pre><\/div>\n\n\n<p>\u8868\u793a\u4e0b\u9762\u884c\u6240\u6709\u5b57\u7b26\u4e32\uff0c\u8d4b\u503c\u7ed9 sql \u53d8\u91cf<\/p>\n\n\n\n<p>\u5b57\u7b26\u4e32\u53d8\u91cf\u4f1a\u53bb\u6389\u524d\u540e\u7a7a\u767d\u7b26\u3002<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">\u6ce8\u91ca<\/h4>\n\n\n\n<p>\u884c\u5f00\u59cb\uff0c\u8fde\u7eed\u4e24\u4e2a # \u8868\u793a\u6ce8\u91ca\u884c\u3002# \u524d\u4e0d\u80fd\u6709\u7a7a\u767d\u5b57\u7b26\u3002<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">\u6570\u7ec4\u53d8\u91cf<\/h4>\n\n\n\n<p>\u53ea\u652f\u6301\u5b57\u7b26\u4e32\u6570\u7ec4<\/p>\n\n\n\n<p>\u6570\u7ec4\u4f7f\u7528\u9017\u53f7\u9694\u5f00\uff0c\u4e0d\u8003\u8651\u6570\u7ec4\u503c\u5f53\u4e2d\u542b\u6709\u9017\u53f7\u7684\u60c5\u51b5<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n## \u6570\u7ec4\u793a\u4f8b\n@@ arr1\nfield1, field2, 333\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\">\u5b57\u5178\u53d8\u91cf<\/h4>\n\n\n\n<p>\u53ea\u652f\u6301 map[string]string \u7c7b\u578b\u3002<\/p>\n\n\n\n<p>\u683c\u5f0f\u4e3a\u5b57\u5178 key + \u5192\u53f7 + value\u3002<\/p>\n\n\n\n<p>\u793a\u4f8b<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n## \u5b57\u5178\u793a\u4f8b\n@@ map1\nuser: name\npass: word\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">tml \u6587\u4ef6\u793a\u4f8b<\/h2>\n\n\n\n<p>\u5bf9\u4e8e\u5b9a\u4e49\u7ed3\u6784\u4f53<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\nstruct {\n\t\tSql   string            `tml:&quot;sql&quot;`\n\t\tUsers &#x5B;]string          `tml:&quot;users&quot;`\n\t\tAges  map&#x5B;string]string `tml:&quot;ages&quot;`\n\t\tCount int               `tml:&quot;count&quot;`\n\t}\n<\/pre><\/div>\n\n\n<p>\u76f8\u5e94\u7684 tml \u6587\u4ef6\u5185\u5bb9\u5982\u4e0b<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ \u6587\u4ef6\u5f00\u59cb\u53ef\u4ee5\u5199\u4e00\u4e9b\u6587\u672c\uff0c\u4f1a\u88ab\u5ffd\u7565\n\n@@ sql\nselect * \n  from table a \n  union join b \n\n## \u8fd9\u662f\u4e00\u884c\u6ce8\u91ca\uff0c\u6ce8\u91ca\u5f00\u5934\u5fc5\u987b\u662f ## \uff0c# \u524d\u4e0d\u80fd\u6709\u7a7a\u683c\n\n@@ users\n## \u4e2d\u95f4\u8fd8\u53ef\u4ee5\u6709\u6ce8\u91ca\nkyle, alice, john\n\n@@ ages\nkyle: 22\nalice: 23\n## \u4e2d\u95f4\u8fd8\u53ef\u4ee5\u6709\u6ce8\u91ca\njohn: 29\n\n@@ count\n## comment\n    7899\n<\/pre><\/div>\n\n\n<p>\u53e6\u4e00\u4e2a\u6211\u5b9e\u9645\u4f7f\u7528\u5230\u7684 tml \u6587\u4ef6\u5185\u5bb9\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n## \u4fee\u590d\u533f\u540d\u5b57\u6bb5\uff0c\u8fd9\u91cc\u7684 target_table.$1 \u53ef\u4ee5\u4e0e meta \u4e2d\u7684\u5b57\u6bb5\u6620\u5c04\u4e0a\uff0c\u4e5f\u5c31\u662f $2 \u4f1a\u8f6c\u6362\u6210 target_table \u7684\u7b2c 2 \u4e2a\u5b57\u6bb5\n\n@@ sql\n\nINSERT INTO\n    TABLE target_table\nSELECT\n    col1,\n    (\n        SELECT\n            MAX(col2)\n        FROM\n            another_table\n        WHERE\n            col1 = source_table.col1\n    )\nFROM\n    source_table;\n\n\n@@ dialect \n\nhive\n\n@@ meta\n\ntarget_table: col1, fixed_col2\n\n@@ bags \n\n1\n\n@@ not_keep_query\n\n1\n\n@@ deps\n\nsource_table.col1 -&gt; target_table.col1\nanother_table.col2 -&gt; target_table.fixed_col2\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">tml \u89e3\u6790\u6e90\u7801<\/h2>\n\n\n\n<p>\u4ee3\u7801\u91cf\u4e0d\u591a\uff0c\u6211\u76f4\u63a5\u653e\u5728\u4e86 Github \u4e0a\uff1a <a href=\"https:\/\/gist.github.com\/kylege\/e4ddaa1c38818083c7244b229adde9cc\">https:\/\/gist.github.com\/kylege\/e4ddaa1c38818083c7244b229adde9cc<\/a><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u6211\u4e4b\u524d\u5728\u9879\u76ee\u5f00\u53d1\u4e2d\u9047\u5230\u4e00\u4e2a\u75db\u70b9\uff0c\u6bd4\u5982\u6211\u5728\u5b9e\u73b0 SQL \u89e3\u6790\u7684\u82e5\u5e72\u529f\u80fd\uff0c\u9700\u8981\u5199\u5927\u91cf\u7684\u5355\u5143\u6d4b\u8bd5\uff0c\u6d4b\u8bd5\u4e2d\u9700\u8981\u6307\u5b9a S [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-8090","post","type-post","status-publish","format-standard","hentry","category-diary"],"_links":{"self":[{"href":"https:\/\/kyle.ai\/blog\/wp-json\/wp\/v2\/posts\/8090","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kyle.ai\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kyle.ai\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kyle.ai\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/kyle.ai\/blog\/wp-json\/wp\/v2\/comments?post=8090"}],"version-history":[{"count":3,"href":"https:\/\/kyle.ai\/blog\/wp-json\/wp\/v2\/posts\/8090\/revisions"}],"predecessor-version":[{"id":8097,"href":"https:\/\/kyle.ai\/blog\/wp-json\/wp\/v2\/posts\/8090\/revisions\/8097"}],"wp:attachment":[{"href":"https:\/\/kyle.ai\/blog\/wp-json\/wp\/v2\/media?parent=8090"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kyle.ai\/blog\/wp-json\/wp\/v2\/categories?post=8090"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kyle.ai\/blog\/wp-json\/wp\/v2\/tags?post=8090"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}