Skip to content
Draft
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Setup Go
uses: actions/setup-go@v5
Expand All @@ -27,7 +27,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Setup Go
uses: actions/setup-go@v5
Expand All @@ -54,7 +54,7 @@ jobs:
run: sqlc generate

- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'adopt'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Update version
id: update-version
Expand Down
132 changes: 68 additions & 64 deletions internal/codegen/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package codegen

import (
"fmt"
"os"
"strings"

"github.com/tandemdude/sqlc-gen-java/internal/core"
"github.com/tandemdude/sqlc-gen-java/poet"
)

var resultSetClass = poet.NewClassName("java.sql", "ResultSet")
var sqlExceptionClass = poet.NewClassName("java.sql", "SQLException")

type IndentStringBuilder struct {
strings.Builder

Expand All @@ -27,86 +30,87 @@ func (b *IndentStringBuilder) WriteIndentedString(level int, s string) int {
return count
}

func (b *IndentStringBuilder) writeSqlcHeader() {
sqlcVersion := os.Getenv("SQLC_VERSION")

b.WriteString("// Code generated by sqlc. DO NOT EDIT.\n")
b.WriteString("// versions:\n")
b.WriteString("// sqlc " + sqlcVersion + "\n")
b.WriteString("// sqlc-gen-java " + core.PluginVersion + "\n")
}

type nullableHelper struct {
ShouldOutput bool
ReturnType string
ReturnType poet.TypeName
ArgType string
}

func (b *IndentStringBuilder) writeNullableHelpers(nullableHelpers core.NullableHelpers, nonNullAnnotation, nullableAnnotation string) []string {
imports := make([]string, 0)
func writeNullableHelpers(ctx *poet.Context, nullableHelpers core.NullableHelpers, nonNullAnnotation, nullableAnnotation poet.Annotation) []poet.Method {
// FIXME: Annotations
var methods []poet.Method

methodTypes := []nullableHelper{
{nullableHelpers.Int, "Integer", "Int"},
{nullableHelpers.Long, "Long", "Long"},
{nullableHelpers.Float, "Float", "Float"},
{nullableHelpers.Double, "Double", "Double"},
{nullableHelpers.Boolean, "Boolean", "Boolean"},
{nullableHelpers.Int, poet.IntBoxed, "Int"},
{nullableHelpers.Long, poet.LongBoxed, "Long"},
{nullableHelpers.Float, poet.FloatBoxed, "Float"},
{nullableHelpers.Double, poet.DoubleBoxed, "Double"},
{nullableHelpers.Boolean, poet.BoolBoxed, "Boolean"},
}

for _, methodType := range methodTypes {
if !methodType.ShouldOutput {
continue
}

b.WriteIndentedString(1, fmt.Sprintf(
"private static %s get%s(%s rs, int col) throws SQLException {\n",
core.Annotate(methodType.ReturnType, nullableAnnotation),
methodType.ArgType,
core.Annotate("ResultSet", nonNullAnnotation),
))
b.WriteIndentedString(2, fmt.Sprintf(
"var colVal = rs.get%s(col); return rs.wasNull() ? null : colVal;\n",
methodType.ArgType,
))
b.WriteIndentedString(1, "}\n")
method := poet.NewMethodBuilder(fmt.Sprintf("get%s", methodType.ArgType), methodType.ReturnType).
WithParameters(poet.NewMethodParam("rs", resultSetClass), poet.NewMethodParam("col", poet.Int)).
WithThrows(sqlExceptionClass).
WithCode(
poet.NewCodeBuilder().
WithStatement("var colVar = rs.get$L(col)", methodType.ArgType).
WithStatement("return rs.wasNull() ? null : colVal").
Build(),
).
Build()

methods = append(methods, method)
}

if nullableHelpers.List {
b.WriteIndentedString(1, fmt.Sprintf(
"private static <T> %s getList(%s rs, int col, Class<T[]> as) throws SQLException {\n",
core.Annotate("List<T>", nullableAnnotation),
core.Annotate("ResultSet", nonNullAnnotation),
))
b.WriteIndentedString(2, "var colVal = rs.getArray(col); return colVal == null ? null : Arrays.asList(as.cast(colVal.getArray()));\n")
b.WriteIndentedString(1, "}\n")

imports = append(imports, "java.util.List")
ctx.Import("java.util.Arrays")
genericParamT := poet.NewGenericParam("T")

method := poet.NewMethodBuilder("getList", poet.ListOf(genericParamT)).
WithGenericParameters(genericParamT).
WithParameters(poet.NewMethodParam("rs", resultSetClass), poet.NewMethodParam("col", poet.Int)).
WithThrows(sqlExceptionClass).
WithCode(
poet.NewCodeBuilder().
WithStatement("var colVal = rs.getArray(col)").
WithStatement("return colVal == null ? null : Arrays.asList(as.cast(colVal.getArray()))").
Build(),
).
Build()

methods = append(methods, method)
}

return imports
return methods
}

func (b *IndentStringBuilder) writeParameter(javaType core.JavaType, name, nonNullAnnotation, nullableAnnotation string) ([]string, error) {
imp, jt, err := core.ResolveImportAndType(javaType.Type)
if err != nil {
return nil, err
}
imports := []string{imp}

if javaType.IsList {
imports = append(imports, "java.util.List")
jt = "List<" + jt + ">"
}

annotation := nonNullAnnotation
if javaType.IsNullable {
annotation = nullableAnnotation
}

newType, unboxed := core.MaybeUnbox(javaType.Type, javaType.IsNullable)
if !unboxed {
newType = core.Annotate(jt, annotation)
}

b.WriteIndentedString(2, newType+" "+name)
return imports, nil
}
//func (b *IndentStringBuilder) writeParameter(javaType core.JavaType, name, nonNullAnnotation, nullableAnnotation string) ([]string, error) {
// imp, jt, err := core.ResolveImportAndType(javaType.Type)
// if err != nil {
// return nil, err
// }
// imports := []string{imp}
//
// if javaType.IsList {
// imports = append(imports, "java.util.List")
// jt = "List<" + jt + ">"
// }
//
// annotation := nonNullAnnotation
// if javaType.IsNullable {
// annotation = nullableAnnotation
// }
//
// newType, unboxed := core.MaybeUnbox(javaType.Type, javaType.IsNullable)
// if !unboxed {
// newType = core.Annotate(jt, annotation)
// }
//
// b.WriteIndentedString(2, newType+" "+name)
// return imports, nil
//}
80 changes: 45 additions & 35 deletions internal/codegen/enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/iancoleman/strcase"
"github.com/tandemdude/sqlc-gen-java/internal/core"
"github.com/tandemdude/sqlc-gen-java/poet"
)

var javaInvalidIdentChars = regexp.MustCompile("[^$\\w]")
Expand All @@ -30,47 +31,56 @@ func enumValueName(value string) string {
return name
}

func BuildEnumFile(engine string, conf core.Config, qualName string, enum core.Enum, defaultSchema string) (string, []byte, error) {
className := EnumClassName(qualName, defaultSchema)
func BuildEnumFile(engine string, config core.Config, qualName string, enum core.Enum, defaultSchema string) (string, []byte, error) {
ctx := poet.NewContext(
config.Package+".models",
poet.WithIndent(strings.Repeat(config.IndentChar, config.CharsPerIndentLevel)),
)

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")
enumName := EnumClassName(qualName, defaultSchema)

enumBuilder := poet.NewEnumBuilder(enumName).
WithAnnotation(
poet.NewAnnotationBuilder(generatedClass).
WithMember("value", "$S", "io.github.tandemdude.sqlc-gen-java").
Build(),
).
WithModifiers(poet.ModifierPublic)

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

// 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")
}
for _, value := range enum.Values {
enumBuilder.WithValue(enumValueName(value), value)
}
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
enumType := poet.NewClassName("", enumName)

enumBuilder.WithMethods(
poet.NewMethodBuilder("getValue", poet.String).
WithCode(
poet.NewCodeBuilder().
WithStatement("return this.value").
Build(),
).
Build(),

poet.NewMethodBuilder("fromValue", enumType).
// FIXME: Make value final String (?)
WithParameters(poet.NewMethodParam("value", poet.String)).
WithCode(
poet.NewCodeBuilder().
WithControlFlow("for (var v : $T.values())", func(cb *poet.CodeBuilder) {
cb.WithRawCode("if (v.value.equals(value)) return v;")
}, enumType).
WithStatement(`throw new IllegalArgumentException("No enum constant with value " + value)`).
Build(),
).
Build(),
)

fileContents := poet.FormatFile(ctx, enumBuilder.Build(), poet.WithFileComment(core.FileHeaderComment))
return fmt.Sprintf("enums/%s.java", enumName), []byte(fileContents), nil
}
Loading
Loading