Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/prod' into prod
Browse files Browse the repository at this point in the history
  • Loading branch information
ZelvaMan committed Jun 22, 2024
2 parents 8d0ed96 + ea1a768 commit 394aa82
Show file tree
Hide file tree
Showing 32 changed files with 513 additions and 51 deletions.
3 changes: 0 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ root = true

[*]
charset = utf-8
end_of_line = crlf
indent_size = 2
indent_style = tab
insert_final_newline = true
Expand All @@ -20,15 +19,13 @@ ij_wrap_on_typing = false
indent_style = tab
indent_size = 2
trim_trailing_whitespace = true
end_of_line = crlf
charset = utf-8
ij_javascript_use_semicolon_after_statement = false

[*.{xaml,html,xml,heex,exs,ex,eex}]
indent_style = tab
indent_size = 2
trim_trailing_whitespace = true
end_of_line = crlf
charset = utf-8

[*.{heex,exs,ex,eex}]
Expand Down
53 changes: 42 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ code today, tomorrow and in 5 years, and you won't have to search for it on inte
## In-house templates, In-house configuration

All configuration, including templates used for code generation are part of the repository. Nothing depends on some service in internet, or a tool installation.
Everything is under your control, versioned, easily updateable.
Everything is under your control, versioned, easily updatable.

## Customization based on your needs

Expand All @@ -51,9 +51,25 @@ In Enterprise development, it is often the case that your internet connection is
have internet at all. In case of digital nomads, you might be currently working in K2 2nd base camp. In all these cases you are covered, db-gen is
self-contained executable, it needs nothing else than configuration and templates.

## Known Limitations
## How to use it

- generating code for functions that return one value acts like the function return void
We usually put the downloaded `db-gen-win.exe`, `db-gen-linux` or both directly to the repo. Yes, the repo gets bigger but in five years, when you have to update your project, you won't have to look for it on the ever forgetting internet.
Also, when it's part of the repo, you can run specific `db-gen generate` as part of your CD\CI and use different templates. Why would you do that? For example, to remove log messages that should be visible only in Development environment. This is what Erlang/Elixir does to speed up their code.

When you run `db-gen` you are offered these two main options:
- `generate` - will run the generation of code
- `routines` - will generate json file that contains definition of all stored functions/procedures that you have defined in `db-gen.json`, this can later be used for offline generation
- `help [command]` - will print out help for specific command with additional details

## How to start

The easiest way, is to:
- take the content from `test` folder
- use the `test/database/testing-db.sql` script to create a test database
- update connection string to proper values in `test/local.db-gen.json`
- download a latest `db-gen` release from [Releases page](https://github.com/KeenMate/db-gen/releases)
- run `./db-gen-win.exe generate` or `./db-gen-linux generate`
- be properly amazed, shocked, stunned!

## Configuration

Expand All @@ -66,7 +82,8 @@ If `--config` flag is not set it will try following default locations

Enable debug logging with `--debug` flag

ConnectionString can be also set with `--connectionString "postgresql://usernmae:password@host:port/database_name"`
ConnectionString can be also set with `--connectionString "postgresql://username:password@host:port/database_name"`


### Local configuration

Expand All @@ -90,7 +107,7 @@ The local config file is not required.

- **ConnectionString (string)**:
- Defines the PostgreSQL database connection string.
- For example `postgresql://usernmae:password@localhost:5432/database_name`
- For example `postgresql://username:password@localhost:5432/database_name`
- **OutputFolder (string)**:
- Specifies the folder where generated code files will be saved.
- It can be relative to the current working directory
Expand Down Expand Up @@ -135,7 +152,12 @@ The local config file is not required.

## Templates

Templates have there information available in `.`
Templates to use are defined in these properties of `db-gen.json`
- DbContextTemplate - this will generate database calls
- ModelTemplate - this will generate models to represent data coming from db
- ProcessorTemplate - this will generate mappers mapping data from db to models

Templates use database metadata in format:

```go
type DbContextData struct {
Expand Down Expand Up @@ -181,11 +203,10 @@ type Routine struct {
ReturnProperties []Property
}




```

Templates themselves are written in Go Templates and can be changed to your liking. You are in charge.

### Case

By default, all fields use camel case.
Expand All @@ -196,7 +217,6 @@ For example:
{{pascalCased $func.FunctionName}}
```


### Mapping override per routines

_TODO Improve this section_
Expand Down Expand Up @@ -224,6 +244,17 @@ In mapping object you can override `MappedName`, `IsNullable`, `MappedType` and
If you only specify `MappedType` it will try to find mapping function in global mappings, stoping generation with error if it didnt.
Setting `MappingFunction` without `MappedType` will do nothing.


#### Parameters

It doesnt make sense to only use some parameter, so you can only change `MappedName`,`MappedType`, and `IsNUllable`
> DISCLAIMER: Needs clarification
It doesn't make sense to only use some parameter, so you can only change `MappedName`,`MappedType`, and `IsNUllable`. This also means that you can't set parameter value to boolean, you can only set it to object with custom mapping

### Overloaded function

> DISCLAIMER: Needs clarification
To prevent a LOT of issue with overloaded functions, you are forced to specify mapped name for each function that has some overload.

The name has to be unique in schema, but checking is not yet implemented, so be careful!!!
77 changes: 77 additions & 0 deletions cmd/databaseChanges.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cmd

import (
"fmt"
"github.com/keenmate/db-gen/private/dbGen"
"github.com/keenmate/db-gen/private/helpers"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"log"
)

var databaseChangesFlags = []helpers.FlagArgument{
helpers.NewBoolFlag(keyUseRoutinesFile, "", false, "Use routines file to databaseChanges code"),
}

var databaseChangesCmd = &cobra.Command{
Use: "database-changes",
Short: "show changes made to database from last generation",
Long: `
prints differences between routines loaded from database/routines file
and routines used during generation
`,
Run: func(cmd *cobra.Command, args []string) {
helpers.BindFlags(cmd, append(commonFlags, databaseChangesFlags...))
_, err := dbGen.ReadConfig(viper.GetString(keyConfig))
if err != nil {
helpers.Exit("configuration error: %s", err)
}

viper.AutomaticEnv() // read in environment variables that match

err = doDatabaseChanges()
if err != nil {
helpers.Exit(err.Error())
}
},
}

func init() {
rootCmd.AddCommand(databaseChangesCmd)

helpers.DefineFlags(databaseChangesCmd, append(commonFlags, databaseChangesFlags...))
}

func doDatabaseChanges() error {
config, err := dbGen.GetAndValidateConfig()
if err != nil {
return fmt.Errorf("error getting config %s", err)
}

helpers.LogDebug("Debug logging is enabled")

// TODO it will be ideal to load build information before loading and validating config
buildInfo, infoExist := dbGen.LoadGenerationInformation(config)

if !infoExist {
return fmt.Errorf("no generation information found")
}

if !buildInfo.CheckVersion() {
return nil
}

var routines []dbGen.DbRoutine

log.Printf("Getting routines...")
routines, err = dbGen.GetRoutines(config)
if err != nil {
return fmt.Errorf("error getting routines: %s", err)
}
log.Printf("Got %d routines", len(routines))

databaseChanges := buildInfo.GetRoutinesChanges(routines)
log.Printf("Database changes:\n" + databaseChanges)

return nil
}
75 changes: 56 additions & 19 deletions cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ package cmd

import (
"fmt"
dbGen2 "github.com/keenmate/db-gen/private/dbGen"
common2 "github.com/keenmate/db-gen/private/helpers"
"github.com/keenmate/db-gen/private/dbGen"
"github.com/keenmate/db-gen/private/helpers"
"github.com/keenmate/db-gen/private/version"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"log"
)

const keyUseRoutinesFile = "useRoutinesFile"

var generateFlags = []common2.FlagArgument{
common2.NewBoolFlag(keyUseRoutinesFile, "", false, "Use routines file to generate code"),
var generateFlags = []helpers.FlagArgument{
helpers.NewBoolFlag(keyUseRoutinesFile, "", false, "Use routines file to generate code"),
}

var generateCmd = &cobra.Command{
Expand All @@ -27,74 +28,110 @@ var generateCmd = &cobra.Command{
`,
Run: func(cmd *cobra.Command, args []string) {
common2.BindFlags(cmd, append(commonFlags, generateFlags...))
_, err := dbGen2.ReadConfig(viper.GetString(keyConfig))
helpers.BindFlags(cmd, append(commonFlags, generateFlags...))
_, err := dbGen.ReadConfig(viper.GetString(keyConfig))
if err != nil {
common2.Exit("configuration error: %s", err)
helpers.Exit("configuration error: %s", err)
}

viper.AutomaticEnv() // read in environment variables that match

err = doGenerate()
if err != nil {
common2.Exit(err.Error())
helpers.Exit(err.Error())
}
},
}

func init() {
rootCmd.AddCommand(generateCmd)

common2.DefineFlags(generateCmd, append(commonFlags, generateFlags...))
helpers.DefineFlags(generateCmd, append(commonFlags, generateFlags...))
}

func doGenerate() error {
timer := helpers.NewTimer()
log.Printf("Getting configurations...")

config, err := dbGen2.GetAndValidateConfig()
config, err := dbGen.GetAndValidateConfig()
if err != nil {
return fmt.Errorf("error getting config %s", err)
}

common2.LogDebug("Debug logging is enabled")
helpers.LogDebug("Debug logging is enabled")
timer.AddEntry("getting config")

var routines []dbGen2.DbRoutine
// TODO it will be ideal to load build information before loading and validating config
buildInfo, infoExist := dbGen.LoadGenerationInformation(config)
timer.AddEntry("loading build information")

if infoExist {
log.Printf("Build information loaded, last build was at %s", buildInfo.Time.String())

if !buildInfo.CheckVersion() {
return nil
}
}

var routines []dbGen.DbRoutine

log.Printf("Getting routines...")
routines, err = dbGen2.GetRoutines(config)
routines, err = dbGen.GetRoutines(config)
if err != nil {
return fmt.Errorf("error getting routines: %s", err)
}
log.Printf("Got %d routines", len(routines))

timer.AddEntry("getting routines")
if config.Debug {
common2.LogDebug("Saving to debug file...")
err = common2.SaveToTempFile(routines, "dbRoutines")
helpers.LogDebug("Saving to debug file...")
err = helpers.SaveToTempFile(routines, "dbRoutines")
if err != nil {
return fmt.Errorf("error saving debug file: %s", err)
}
timer.AddEntry("saving debug file")
}

if infoExist {
// TODO we should only show changes of routines after filtering
changesMsg := buildInfo.GetRoutinesChanges(routines)
log.Printf("Database changes:\n" + changesMsg)
} else {
log.Printf("No previous build information found")
}

log.Printf("Preprocessing...")
processedFunctions, err := dbGen2.Process(routines, config)
processedFunctions, err := dbGen.Process(routines, config)
if err != nil {
return fmt.Errorf("error preprocessing: %s", err)
}
log.Printf("After preprocessing %d - %d = %d functions left", len(routines), len(routines)-len(processedFunctions), len(processedFunctions))
timer.AddEntry("preprocessing")

if config.Debug {
common2.LogDebug("Saving to debug file...")
err = common2.SaveToTempFile(processedFunctions, "mapped")
helpers.LogDebug("Saving to debug file...")
err = helpers.SaveToTempFile(processedFunctions, "mapped")
if err != nil {
return fmt.Errorf("error saving debug file: %s", err)
}
timer.AddEntry("saving debug file")

}

log.Printf("Generating...")
err = dbGen2.Generate(processedFunctions, config)
err = dbGen.Generate(processedFunctions, config)
if err != nil {
return fmt.Errorf("error generating: %s", err)
}
timer.AddEntry("generating files")

err = dbGen.SaveGenerationInformation(config, routines, version.GetVersion())
if err != nil {
log.Printf("Error saving generation information: %v", err)
}

timer.AddEntry("saving generation info")
timer.Finish()
log.Printf(timer.String())
return nil
}
Loading

0 comments on commit 394aa82

Please sign in to comment.