Skip to content

Commit a72e739

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 a72e739

12 files changed

Lines changed: 1014 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: 126 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ package sqlc
66

77
import (
88
"encoding/json"
9+
"fmt"
910
"os"
11+
"slices"
12+
"strings"
1013
)
1114

1215
// Catalog represents the root structure of a sqlc catalog file.
@@ -35,7 +38,8 @@ func LoadCatalog(path string) (*Catalog, error) {
3538
type Schema struct {
3639
Name string `json:"name"`
3740
Tables []Table `json:"tables,omitempty"`
38-
Attrs
41+
// Inherit common attributes
42+
Attributes
3943
}
4044

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

51105
// Column represents a table column with its name, data type, and nullability.
52106
type Column struct {
53107
Name string `json:"name"`
54108
Type string `json:"type,omitempty"`
55109
Null bool `json:"null,omitempty"`
56-
Attrs
110+
// Inherit common attributes
111+
Attributes
57112
}
58113

59114
// Index represents a database index on one or more columns or expressions.
@@ -63,6 +118,13 @@ type Index struct {
63118
Parts []IndexPart `json:"parts,omitempty"`
64119
}
65120

121+
// HasExpr checks if the index contains any expression-based parts.
122+
func (x *Index) HasExpr() bool {
123+
return slices.ContainsFunc(x.Parts, func(x IndexPart) bool {
124+
return x.Expr != ""
125+
})
126+
}
127+
66128
// IndexPart represents a single part of an index, which can be either a column or an expression.
67129
type IndexPart struct {
68130
Desc bool `json:"desc,omitempty"`
@@ -81,9 +143,68 @@ type ForeignKey struct {
81143
} `json:"references"`
82144
}
83145

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

0 commit comments

Comments
 (0)