holistics / dbml

Database Markup Language (DBML), designed to define and document database structures
https://dbml.org
Apache License 2.0
2.9k stars 170 forks source link

Generate DBML from JS object #514

Open loicknuchel opened 9 months ago

loicknuchel commented 9 months ago

Hi, First, thank you for this project and the libraries <3

I have an object representing my db and I'm looking to generate DBML from it. Looking at https://dbml.dbdiagram.io/js-module/#class-modelexporter I see I can generate DBML using:

ModelExporter.export(database, 'postgres', false)

My issue is, I don't know how to generate a database value with Database type :disappointed:

In the example it's build using the parser but I don't have DBML as input.

I saw the Database class is exported here: dbml-core/src/model_structure/database.js, and I tried to import it using:

import Database from "@dbml/core/types/model_structure/database";

But it gives me the error: Cannot find module '@dbml/core/types/model_structure/database' from 'src/dbml.ts'

And when I try:

import Database from "@dbml/core/model_structure/database";

I got the error: Cannot find module '@dbml/core/model_structure/database' or its corresponding type declarations.

Do you have any idea how I can configure TypeScript to match modules and type definitions or how I can create a Database class? I think it would be nice to export the models in model_structure in the index.js, what do you think? Also, I don't know the difference between a Database and a NormalizedDatabase, is there any explanation somewhere?

Thanks!

loicknuchel commented 9 months ago

Looking a bit more at the API I found I could use the JSON parser to generate a Database:

const db: Database = (new Parser(undefined)).parse(JSON.stringify({...}), 'json')

It seems the JSON parser takes a RawDatabase type, which is better but comes back to the same issue with the Table model for example (can't build it, at least for now).

So I tried to export to JSON and then import:

const dbmb = `...`
const db: Database = (new Parser(undefined)).parse(dbml, 'dbmlv2')
const json: string = ModelExporter.export(db, 'json', false)
const db2: Database = (new Parser(undefined)).parse(json, 'json')
const generated = ModelExporter.export(db2, 'dbml', false)

But this fails when parsing the JSON, on the 4th line, with Can't find table "null"."posts". Looks like the JSON output is not a valid JSON input :disappointed: Seems that the relations don't work well on JSON, they fail both with and without schema. Is there somewhere a documentation with the expected JSON structure for the Parser?

In the RawDatabase I see there is both schemas (with tables inside) and tables directly at the root. How does it work? Should I duplicate tables in both?

Edit: In investigated more and succedeed to make it work when the relation is not in the same schema as tables (maybe linked to looking for tables before adding them?). This DBML is working:

Table s1.users {
  id integer [primary key]
  name varchar
}

Table s2.posts {
  id int [pk]
  author integer
}

Ref: s1.users.id < s2.posts.author

It works because the relation is cross schema and then put inside the public one. If both tables are in the same schema, the relation will be inside as well and it fails.

NQPhuc commented 9 months ago

Hi @loicknuchel

Can you help provide the JSON that failed initially (the one that give error Can't find table "null"."posts"). We'll take a look into this.

loicknuchel commented 9 months ago

I used the example from your documentation: https://dbml.dbdiagram.io/docs/#take-a-look-at-an-example-below Here is my full code:

test('https://github.com/holistics/dbml/issues/514',  () => {
    const content = `Table users {
    id integer
    username varchar
    role varchar
    created_at timestamp
}

Table posts {
    id integer [primary key]
    title varchar
    body text [note: 'Content of the post']
    user_id integer
    created_at timestamp
}

Ref: posts.user_id > users.id // many-to-one`
    const dbFromDbml = (new Parser(undefined)).parse(content, 'dbmlv2')
    const json: string = ModelExporter.export(dbFromDbml, 'json', false)
    console.log('json', json)
    const dbFromJson = (new Parser(undefined)).parse(json, 'json')
    console.log('json parsed!')
    const generated = ModelExporter.export(dbFromJson, 'dbml', false)
    console.log('dbml generated', generated)
})

It fails when trying to parse the json: (new Parser(undefined)).parse(json, 'json') with the error:

Can't find table "null"."posts"

Here is the printed JSON:

{
  "schemas": [{
    "name": "public",
    "note": "Default Public Schema",
    "tables": [{
      "name": "users",
      "alias": null,
      "note": null,
      "fields": [
        {"name": "id", "type": {"schemaName": null, "type_name": "integer", "args": null}, "unique": false, "pk": false, "note": null},
        {"name": "username", "type": {"schemaName": null, "type_name": "varchar", "args": null}, "unique": false, "pk": false, "note": null},
        {"name": "role", "type": {"schemaName": null, "type_name": "varchar", "args": null}, "unique": false, "pk": false, "note": null},
        {"name": "created_at", "type": {"schemaName": null, "type_name": "timestamp", "args": null}, "unique": false, "pk": false, "note": null}
      ],
      "indexes": []
    }, {
      "name": "posts",
      "alias": null,
      "note": null,
      "fields": [
        {"name": "id", "type": {"schemaName": null, "type_name": "integer", "args": null}, "unique": false, "pk": true, "not_null": false, "note": null, "increment": false},
        {"name": "title", "type": {"schemaName": null, "type_name": "varchar", "args": null}, "unique": false, "pk": false, "note": null},
        {"name": "body", "type": {"schemaName": null, "type_name": "text", "args": null}, "unique": false, "pk": false, "not_null": false, "note": "Content of the post", "increment": false},
        {"name": "user_id", "type": {"schemaName": null, "type_name": "integer", "args": null}, "unique": false, "pk": false, "note": null},
        {"name": "created_at", "type": {"schemaName": null, "type_name": "timestamp", "args": null}, "unique": false, "pk": false, "note": null}
      ],
      "indexes": []
    }],
    "enums": [],
    "tableGroups": [],
    "refs": [{
      "name": null,
      "endpoints": [
        {"schemaName": null, "tableName": "posts", "fieldNames": ["user_id"], "relation": "*"},
        {"schemaName": null, "tableName": "users", "fieldNames": ["id"], "relation": "1"}
      ]
    }]
  }]
}

If the relation is not in the same schema as tables, it works. BTW, it also fails with any TableGroup ^^

But I'm only using the JSON parser to build a Database class from my own structure as I couldn't create it directly :disappointed: Is it an intended usage to generate DBML from JavaScript? (coming with my own data)

klerick commented 6 months ago

The problem is here try to find schema, but it is not exist in database object, because here the object is creating yet.