Skip to content

Commit de0b1d8

Browse files
committed
feat: add SQL query template generation with CRUD operations
- Add github.com/go-openapi/inflect dependency for string transformations - Extend catalog types with methods for retrieving columns and unique keys - Add condition and argument types for SQL query building - Implement template-based SQL generation in generator.go - Create embedded SQL template with comprehensive CRUD operations - Support multiple query execution modes (:one, :exec, :many, :batch*, :copyfrom) - Add template helper functions for pluralization and camelCase conversion Signed-off-by: Svetlin Ralchev <iamralch@users.noreply.github.com>
1 parent 12e573f commit de0b1d8

12 files changed

Lines changed: 1018 additions & 10 deletions

File tree

‎.octocov.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ coverage:
33
path: .github/coverage.svg
44
paths:
55
- coverage.out
6-
acceptable: 0%
6+
acceptable: 94%
77
comment:
88
if: is_pull_request
99
deletePrevious: true

‎go.mod‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ tool (
88
)
99

1010
require (
11+
github.com/go-openapi/inflect v0.21.3
1112
github.com/onsi/ginkgo/v2 v2.27.2
1213
github.com/onsi/gomega v1.38.2
1314
github.com/urfave/cli/v3 v3.6.1

‎go.sum‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,6 +1431,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
14311431
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
14321432
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
14331433
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
1434+
github.com/go-openapi/inflect v0.21.3 h1:TmQvw+9eLrsNp4X0BBQacEZZtAnzk2z1FaLdQQJsDiU=
1435+
github.com/go-openapi/inflect v0.21.3/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
14341436
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
14351437
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
14361438
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=

‎integration/suite_test.go‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package integration_test
22

33
import (
4+
"fmt"
5+
"os"
46
"os/exec"
7+
"path/filepath"
58
"testing"
69

10+
"github.com/onsi/gomega/gexec"
11+
712
. "github.com/onsi/ginkgo/v2"
813
. "github.com/onsi/gomega"
9-
"github.com/onsi/gomega/gexec"
1014
)
1115

1216
func TestIntegration(t *testing.T) {
@@ -24,7 +28,15 @@ var _ = BeforeSuite(func() {
2428
Expect(path).NotTo(BeEmpty())
2529
// prepare the command
2630
command = exec.Command(path)
27-
command.Env = append(command.Env, "SQLC_CONFIG_FILE=../internal/sqlc/config_test.yaml")
28-
command.Env = append(command.Env, "SQLC_CATALOG_FILE=../internal/sqlc/catalog_test.json")
31+
command.Dir, _ = os.MkdirTemp("", "sqcl-gen-test-*")
32+
command.Env = append(command.Env, GetFileEnv("SQLC_CONFIG_FILE", "../internal/sqlc/config_test.yaml"))
33+
command.Env = append(command.Env, GetFileEnv("SQLC_CATALOG_FILE", "../internal/sqlc/catalog_test.json"))
2934
})
3035
})
36+
37+
// GetFileEnv is a helper to set environment variables from file contents in tests
38+
func GetFileEnv(name, key string) string {
39+
dir, err := os.Getwd()
40+
Expect(err).NotTo(HaveOccurred())
41+
return fmt.Sprintf("%v=%v", name, filepath.Join(dir, key))
42+
}

‎internal/sqlc/catalog.go‎

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
package sqlc
66

77
import (
8+
"cmp"
89
"encoding/json"
10+
"fmt"
911
"os"
12+
"slices"
13+
"strings"
1014
)
1115

1216
// Catalog represents the root structure of a sqlc catalog file.
@@ -35,7 +39,8 @@ func LoadCatalog(path string) (*Catalog, error) {
3539
type Schema struct {
3640
Name string `json:"name"`
3741
Tables []Table `json:"tables,omitempty"`
38-
Attrs
42+
// Inherit common attributes
43+
Attributes
3944
}
4045

4146
// Table represents a database table with its columns, indexes, and constraints.
@@ -45,15 +50,69 @@ type Table struct {
4550
Indexes []Index `json:"indexes,omitempty"`
4651
PrimaryKey *Index `json:"primary_key,omitempty"`
4752
ForeignKeys []ForeignKey `json:"foreign_keys,omitempty"`
48-
Attrs
53+
// Inherit common attributes
54+
Attributes
55+
}
56+
57+
// GetColumn retrieves a column by name from the table.
58+
func (x *Table) GetColumn(name string) *Column {
59+
for _, column := range x.Columns {
60+
if column.Name == name {
61+
return &column
62+
}
63+
}
64+
65+
return nil
66+
}
67+
68+
// GetNonUniqueIndexes retrieves all non-unique indexes from the table.
69+
func (x *Table) GetNonUniqueIndexes() []*Index {
70+
var keys []*Index
71+
72+
for _, index := range x.Indexes {
73+
if index.HasExpr() {
74+
continue
75+
}
76+
77+
if !index.Unique {
78+
index.Name = cmp.Or(index.Name, "non-unique key")
79+
keys = append(keys, &index)
80+
}
81+
}
82+
83+
return keys
84+
}
85+
86+
// GetUniqueKeys retrieves all unique keys (primary key and unique indexes) from the table.
87+
func (x *Table) GetUniqueKeys() []*Index {
88+
var keys []*Index
89+
90+
if x.PrimaryKey != nil {
91+
x.PrimaryKey.Name = cmp.Or(x.PrimaryKey.Name, "primary key")
92+
keys = append(keys, x.PrimaryKey)
93+
}
94+
95+
for _, index := range x.Indexes {
96+
if index.HasExpr() {
97+
continue
98+
}
99+
100+
if index.Unique {
101+
index.Name = cmp.Or(index.Name, "unique key")
102+
keys = append(keys, &index)
103+
}
104+
}
105+
106+
return keys
49107
}
50108

51109
// Column represents a table column with its name, data type, and nullability.
52110
type Column struct {
53111
Name string `json:"name"`
54112
Type string `json:"type,omitempty"`
55113
Null bool `json:"null,omitempty"`
56-
Attrs
114+
// Inherit common attributes
115+
Attributes
57116
}
58117

59118
// Index represents a database index on one or more columns or expressions.
@@ -63,6 +122,13 @@ type Index struct {
63122
Parts []IndexPart `json:"parts,omitempty"`
64123
}
65124

125+
// HasExpr checks if the index contains any expression-based parts.
126+
func (x *Index) HasExpr() bool {
127+
return slices.ContainsFunc(x.Parts, func(x IndexPart) bool {
128+
return x.Expr != ""
129+
})
130+
}
131+
66132
// IndexPart represents a single part of an index, which can be either a column or an expression.
67133
type IndexPart struct {
68134
Desc bool `json:"desc,omitempty"`
@@ -81,9 +147,68 @@ type ForeignKey struct {
81147
} `json:"references"`
82148
}
83149

84-
// Attrs represents common attributes that can be applied to schemas, tables, and columns.
150+
// Condition represents a simple equality condition between a column and an argument.
151+
type Condition struct {
152+
Column *Column
153+
Argument *Argument
154+
}
155+
156+
// String returns the string representation of the Condition for use in SQL queries.
157+
func (x *Condition) String() string {
158+
return fmt.Sprintf("%s = %v", x.Column.Name, x.Argument)
159+
}
160+
161+
// CompositeCondition represents a combination of multiple conditions using a logical operator (e.g., AND, OR).
162+
type CompositeCondition struct {
163+
Operator string
164+
Conditions []fmt.Stringer
165+
}
166+
167+
// AddColumn adds a new condition for the specified column to the CompositeCondition.
168+
func (x *CompositeCondition) AddColumn(column *Column) {
169+
x.Conditions = append(x.Conditions,
170+
&Condition{
171+
Column: column,
172+
Argument: &Argument{
173+
Column: column,
174+
},
175+
},
176+
)
177+
}
178+
179+
// String returns the string representation of the CompositeCondition for use in SQL queries.
180+
func (x *CompositeCondition) String() string {
181+
var items []string
182+
183+
for _, condition := range x.Conditions {
184+
if expression := condition.String(); expression != "" {
185+
items = append(items, expression)
186+
}
187+
}
188+
189+
if len(items) == 0 {
190+
return ""
191+
}
192+
193+
return strings.Join(items, fmt.Sprintf(" %s ", x.Operator))
194+
}
195+
196+
// Argument represents SQL argument corresponding to a column.
197+
type Argument struct {
198+
Column *Column `json:"column"`
199+
}
200+
201+
// String returns the string representation of the Argument for use in SQL queries.
202+
func (x *Argument) String() string {
203+
if x.Column.Null {
204+
return fmt.Sprintf("sqlc.narg(%s)", x.Column.Name)
205+
}
206+
return fmt.Sprintf("sqlc.arg(%s)", x.Column.Name)
207+
}
208+
209+
// Attributes represents common attributes that can be applied to schemas, tables, and columns.
85210
// These are typically dialect-specific metadata.
86-
type Attrs struct {
211+
type Attributes struct {
87212
Comment string `json:"comment,omitempty"`
88213
Charset string `json:"charset,omitempty"`
89214
Collate string `json:"collate,omitempty"`

0 commit comments

Comments
 (0)