iOliverNguyen / sqlgen

This package generates SQL methods from model
MIT License
18 stars 3 forks source link

DSL for declaring code generation #7

Open iOliverNguyen opened 6 years ago

iOliverNguyen commented 6 years ago

The first prototype employs [goderive]() as DSL (example) for declaring code generation.

The next version should define its own DSL for more clarification and better error messages. The DSL can be put inside Go code or as separate files like protobuf. We don't intend to employ the whole SQL syntax, only a subset that makes sense.

Note that, unlike SQL, our keywords must be lowercase (generate) or uppercase (GENERATE) but not mixed (Generate or ~GeneRATE~).

Generate structs from table

//sqlgen: generate from "user"
type User struct {
    ID   int64
    Name string
}

Short-hand syntax. Both struct and table name can be inferred from the struct.

//sqlgen: generate
type Account struct {
    ID   int64
    Name string
}

Full syntax with plural form:

//sqlgen: generate AccountUser (plural AccountUsers) from "account_user"
type AccountUser struct {
    AccountID int64
    UserID    int64
    Role      string
}

Join between multiple tables

//sqlgen: generate from "user"
//        join account_user on  "user".id = account_user.user_id
//        join account      on account.id = account_user.account_id
type UserJoinAccount struct {
    User        *User
    AccountUser *AccountUser
    Account     *Account
}

The above can be written in full syntax:

//sqlgen: generate UserJoinAccount (plural UserJoinAccounts)
//        from "user"         (User)        as u
//        join "account_user" (AccountUser) as au on u.id = au.user_id
//        join "account"      (Account)     as a  on a.id = au.account_id
type UserJoinAccount struct {
    User        *User
    AccountUser *AccountUser
    Account     *Account
}

All statements in a single comment block

/*
sqlgen:

generate User from "user";
generate Account from account;
generate AccountUser from account_user;
generate UserJoinAccount
  from "user"
  join account_user on  "user".id = account_user.user_id
  join account      on account.id = account_user.account_id
*/

Full syntax:

generate User (plural Users) from "user";
generate Account (plural Accounts) from "account";
generate AccountUser (plural AccountUsers) from "account_user";
generate UserJoinAccount 
  from "user"         (User)        as u
  join "account_user" (AccountUser) as au on u.id = au.user_id
  join "account"      (Account)     as a  on a.id = au.account_id;
iOliverNguyen commented 6 years ago
iOliverNguyen commented 6 years ago

Rules about using sqlgen: inside a comment:

  1. sqlgen: must appear at the start of the line (can be prefixed by other space characters). The following are valid:
//sqlgen: generate User

// sqlgen: generate User

// sqlgen:generate User

This one is INVALID:

// TEXT sqlgen: generate User

This one is also INVALID (notice missing :):

// sqlgen generate User
  1. There is no difference between using a single /* block or a block with multiple lines prefixed by //. The two following are the same and are parsed as generate User from "user"
/* sqlgen: generate User
       from "user"
*/

// sqlgen: generate User
//     from "user"

But the following is treated as two distinct blocks, thus is parsed as generate User (without the from "user" part!).

/* sqlgen: generate User */
/*     from "user"       */
  1. Inside Go comments, one can use // to start another (nested) comment.
// sqlgen: generate User from "user" // TEXT
  1. The sqlgen: annotation spans multiple lines until a comment line with only space characters.
// TEXT
// sqlgen: generate User
//     from "user" // TEXT
//
// TEXT

The above comment group is parsed as generate User from "user".

  1. Each sqlgen: annotation starts a new block, thus the following comment group is parsed as two distinct blocks and the second one is invalid.
// sqlgen: generete User
// sqlgen: from "User"
  1. A sqlgen: block immediate prefixes a type declaration is attached to the type, thus can get missing information from it.
// sqlgen: generate from "user"
type User struct {}

The above is the same as generate User from "user" while the standalone generate from "user" is invalid (because it does not know which type to attach to).

// standalone, INVALID
// sqlgen: generate from "user"

type User struct{}
// standalone, VALID
// sqlgen: generate User from "user"

type User struct{}
  1. A sqlgen: block can only be attached to global type declaration. It does not work on local type definition. The following is treated as standalone block.
func Foo() {
  // standalone, INVALID
  // sqlgen: generate from "user"
  type User struct{}
}
  1. Only one sqlgen: block can be attached to a single type declaration.
// VALID
// sqlgen: generate
type User struct {}

// VALID
// sqlgen: generate
type (
    User struct{}
)

// INVALID
// sqlgen: generate
type (
  type User struct {}
  type Account struct{}
)

// INVALID
// sqlgen: generate
type (
  type User struct {}

  // sqlgen: generate
  type Account struct{}
)

// VALID
type (
  // sqlgen: generate
  type User struct {}

  // sqlgen: generate
  type Account struct{}
)