Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,10 @@ You should ensure that the `sha256` value in your `sqlc.yaml` is correct for thi

## Planned Features

- `MySQL` support
- `SQLite` support
- Improved parameter naming

**Tentative:**

- r2dbc support
- Support for PostgreSQL enum types
- copyfrom support where possible [ref](https://www.baeldung.com/jdbc-batch-processing)
75 changes: 75 additions & 0 deletions internal/codegen/enums.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package codegen

import (
"fmt"
"github.com/iancoleman/strcase"
"github.com/tandemdude/sqlc-gen-java/internal/core"
"regexp"
"strings"
"unicode"
"unicode/utf8"
)

var javaInvalidIdentChars = regexp.MustCompile("[^$\\w]")

func EnumClassName(qualifiedName, defaultSchema string) string {
return strcase.ToCamel(strings.TrimPrefix(qualifiedName, defaultSchema+"."))
}

func enumValueName(value string) string {
rep := strings.NewReplacer("-", "_", ":", "_", "/", "_", ".", "_")
name := rep.Replace(value)
name = strings.ToUpper(name)
name = javaInvalidIdentChars.ReplaceAllString(name, "")

r, _ := utf8.DecodeRuneInString(name)
if unicode.IsDigit(r) {
name = "_" + name
}
return name
}

func BuildEnumFile(engine string, conf core.Config, qualName string, enum core.Enum, defaultSchema string) (string, []byte, error) {
className := EnumClassName(qualName, defaultSchema)

sb := IndentStringBuilder{indentChar: conf.IndentChar, charsPerIndentLevel: conf.CharsPerIndentLevel}
sb.writeSqlcHeader()
sb.WriteString("\n")
sb.WriteString("package " + conf.Package + ".enums;\n")
sb.WriteString("\n")
sb.WriteString("import javax.annotation.processing.Generated;\n")
sb.WriteString("\n")
sb.WriteString("@Generated(\"io.github.tandemdude.sqlc-gen-java\")\n")
sb.WriteString("public enum " + className + " {\n")

if engine == "mysql" {
sb.WriteIndentedString(1, "BLANK(\"\"),\n")
}

// write other values
for i, value := range enum.Values {
name := enumValueName(value)
sb.WriteIndentedString(1, fmt.Sprintf("%s(\"%s\")", name, value))

if i < len(enum.Values)-1 {
sb.WriteString(",\n")
}
}
sb.WriteString(";\n\n")
sb.WriteIndentedString(1, "private final String value;\n\n")
sb.WriteIndentedString(1, className+"(final String value) {\n")
sb.WriteIndentedString(2, "this.value = value;\n")
sb.WriteIndentedString(1, "}\n\n")
sb.WriteIndentedString(1, "public String getValue() {\n")
sb.WriteIndentedString(2, "return this.value;")
sb.WriteIndentedString(1, "}\n\n")
sb.WriteIndentedString(1, "public static "+className+" fromValue(final String value) {\n")
sb.WriteIndentedString(2, "for (var v : "+className+".values()) {\n")
sb.WriteIndentedString(3, "if (v.value.equals(value)) return v;\n")
sb.WriteIndentedString(2, "}\n")
sb.WriteIndentedString(2, "throw new IllegalArgumentException(\"No enum constant with value \" + value);\n")
sb.WriteIndentedString(1, "}\n")
sb.WriteString("}\n")

return fmt.Sprintf("enums/%s.java", className), []byte(sb.String()), nil
}
3 changes: 3 additions & 0 deletions internal/codegen/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ func BuildModelFile(config core.Config, name string, model []core.QueryReturn) (
header.WriteString("\n")
header.WriteString("package " + config.Package + ".models;\n")
header.WriteString("\n")
header.WriteString("import javax.annotation.processing.Generated;\n")
header.WriteString("\n")

body := NewIndentStringBuilder(config.IndentChar, config.CharsPerIndentLevel)
body.WriteString("\n")
body.WriteString("@Generated(\"io.github.tandemdude.sqlc-gen-java\")\n")
body.WriteString("public record " + strcase.ToCamel(name) + "(\n")
for i, ret := range model {
imps, err := body.writeParameter(ret.JavaType, ret.Name, nonNullAnnotation, nullableAnnotation)
Expand Down
13 changes: 9 additions & 4 deletions internal/codegen/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,14 @@ func completeMethodBody(sb *IndentStringBuilder, q core.Query, embeddedModels co
}
}

func BuildQueriesFile(config core.Config, queryFilename string, queries []core.Query, embeddedModels core.EmbeddedModels, nullableHelpers core.NullableHelpers) (string, []byte, error) {
func BuildQueriesFile(engine string, config core.Config, queryFilename string, queries []core.Query, embeddedModels core.EmbeddedModels, nullableHelpers core.NullableHelpers) (string, []byte, error) {
className := strcase.ToCamel(strings.TrimSuffix(queryFilename, ".sql"))
className = strings.TrimSuffix(className, "Query")
className = strings.TrimSuffix(className, "Queries")
className += "Queries"

imports := make([]string, 0)
imports = append(imports, "java.sql.SQLException", "java.sql.ResultSet", "java.util.Arrays")
imports = append(imports, "java.sql.SQLException", "java.sql.ResultSet", "java.util.Arrays", "javax.annotation.processing.Generated")

var nonNullAnnotation string
if config.NonNullAnnotation != "" {
Expand All @@ -148,6 +148,7 @@ func BuildQueriesFile(config core.Config, queryFilename string, queries []core.Q
body := NewIndentStringBuilder(config.IndentChar, config.CharsPerIndentLevel)
body.WriteString("\n")
// Add the class declaration and constructor
body.WriteString("@Generated(\"io.github.tandemdude.sqlc-gen-java\")\n")
body.WriteString("public class " + className + " {\n")
body.WriteIndentedString(1, "private final java.sql.Connection conn;\n\n")
body.WriteIndentedString(1, "public "+className+"(java.sql.Connection conn) {\n")
Expand Down Expand Up @@ -238,7 +239,11 @@ func BuildQueriesFile(config core.Config, queryFilename string, queries []core.Q
}

methodBody := NewIndentStringBuilder(config.IndentChar, config.CharsPerIndentLevel)
methodBody.WriteIndentedString(2, "var stmt = conn.prepareStatement("+q.MethodName+");\n")
if q.Command == core.ExecResult {
methodBody.WriteIndentedString(2, "var stmt = conn.prepareStatement("+q.MethodName+", java.sql.Statement.RETURN_GENERATED_KEYS);\n")
} else {
methodBody.WriteIndentedString(2, "var stmt = conn.prepareStatement("+q.MethodName+");\n")
}

// write the method signature
body.WriteString("\n")
Expand All @@ -259,7 +264,7 @@ func BuildQueriesFile(config core.Config, queryFilename string, queries []core.Q
body.WriteString(",\n")
}

methodBody.WriteIndentedString(2, arg.BindStmt()+"\n")
methodBody.WriteIndentedString(2, arg.BindStmt(engine)+"\n")
}
body.WriteString("\n")
body.WriteIndentedString(1, ") throws SQLException {\n")
Expand Down
34 changes: 32 additions & 2 deletions internal/core/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type JavaType struct {
Type string
IsList bool
IsNullable bool
IsEnum bool
}

type QueryArg struct {
Expand Down Expand Up @@ -68,7 +69,7 @@ var typeToJavaSqlTypeConst = map[string]string{
"Double": "DOUBLE",
}

func (q QueryArg) BindStmt() string {
func (q QueryArg) BindStmt(engine string) string {
typeOnly := q.JavaType.Type[strings.LastIndex(q.JavaType.Type, ".")+1:]

if q.JavaType.IsList {
Expand All @@ -94,6 +95,21 @@ func (q QueryArg) BindStmt() string {
return fmt.Sprintf("%s == null ? stmt.setNull(%d, java.sql.Types.%s) : %s", q.Name, q.Number, javaSqlType, rawSet)
}

if q.JavaType.IsEnum {
// postgres doesn't like it if you setString an enum directly unfortunately
if engine == "postgresql" {
if q.JavaType.IsNullable {
return fmt.Sprintf("stmt.setObject(%d, %s == null ? null : %s.getValue(), java.sql.Types.OTHER);", q.Number, q.Name, q.Name)
}
return fmt.Sprintf("stmt.setObject(%d, %s.getValue(), java.sql.Types.OTHER);", q.Number, q.Name)
}

if q.JavaType.IsNullable {
return fmt.Sprintf("stmt.setString(%d, %s == null ? null : %s.getValue());", q.Number, q.Name, q.Name)
}
return fmt.Sprintf("stmt.setString(%d, %s.getValue());", q.Number, q.Name)
}

return fmt.Sprintf("stmt.setObject(%d, %s);", q.Number, q.Name)
}

Expand All @@ -107,7 +123,6 @@ func (q QueryReturn) ResultStmt(number int) string {
typeOnly := q.JavaType.Type[strings.LastIndex(q.JavaType.Type, ".")+1:]

if q.JavaType.IsList {
// TODO - check for nullable array support
if q.JavaType.IsNullable {
return fmt.Sprintf("getList(results, %d, %s[].class)", number, typeOnly)
}
Expand All @@ -127,6 +142,13 @@ func (q QueryReturn) ResultStmt(number int) string {
return fmt.Sprintf("results.get%s(%d)", typeOnly, number)
}

if q.JavaType.IsEnum {
if q.JavaType.IsNullable {
return fmt.Sprintf("Optional.ofNullable(results.getString(%d)).map(%s::fromValue).orElse(null)", number, typeOnly)
}
return fmt.Sprintf("%s.fromValue(results.getString(%d))", typeOnly, number)
}

return fmt.Sprintf("results.getObject(%d, %s.class)", number, typeOnly)
}

Expand All @@ -149,7 +171,15 @@ type NullableHelpers struct {
List bool
}

type Enum struct {
Schema string
Name string
Values []string
}

type (
Queries map[string][]Query
EmbeddedModels map[string][]QueryReturn
// Enums is a map of "schema_name.enum_name" to enum value.
Enums map[string]Enum
)
Loading