2
2
package main
3
3
4
4
import (
5
+ _ "embed"
5
6
"errors"
6
7
"flag"
7
8
"fmt"
19
20
onlyFlag = flag .String ("only" , "" , "Limit output to 'schema' or 'queries'" )
20
21
)
21
22
22
- var errBadArgument = errors .New ("bad argument" )
23
+ var (
24
+ errBadArgument = errors .New ("bad argument" )
25
+ errInvalidSmartColumn = fmt .Errorf ("%w: invalid <smart-column>" , errBadArgument )
26
+ )
23
27
24
28
func main () {
25
29
flag .CommandLine .SetOutput (os .Stdout )
@@ -35,34 +39,13 @@ func main() {
35
39
}
36
40
}
37
41
42
+ //go:embed usage.txt
43
+ var usage string
44
+
38
45
//goland:noinspection GoUnhandledErrorResult
39
46
func printUsage () {
40
47
w := flag .CommandLine .Output ()
41
- fmt .Fprintln (w , "sqlcup - generate SQL statements for sqlc (https://sqlc.dev)" )
42
- fmt .Fprintln (w )
43
- fmt .Fprintln (w , "Synopsis:" )
44
- fmt .Fprintln (w , " sqlcup [options] <name> <column> ..." )
45
- fmt .Fprintln (w )
46
- fmt .Fprintln (w , "Description:" )
47
- fmt .Fprintln (w , " sqlcup prints SQL statements to stdout. The <name> argument given to sqlcup" )
48
- fmt .Fprintln (w , " must be of the form <singular>/<plural> where <singular> is the name of the" )
49
- fmt .Fprintln (w , " Go struct and <plural> is the name of the database table." )
50
- fmt .Fprintln (w , " sqlcup capitalizes those names where required." )
51
- fmt .Fprintln (w )
52
- fmt .Fprintln (w , " Each <column> arguments given to sqlcup defines a database column and must" )
53
- fmt .Fprintln (w , " be of the form <name>:<type>[:<constraint>]. <name>, <type> and the" )
54
- fmt .Fprintln (w , " optional <constraint> are used to generate a CREATE TABLE statement." )
55
- fmt .Fprintln (w , " In addition, <name> also appears in the SQL queries. sqlcup never" )
56
- fmt .Fprintln (w , " capitalizes those names." )
57
- fmt .Fprintln (w )
58
- fmt .Fprintln (w , " If any part of a <column> contains a space, it may be necessary to add" )
59
- fmt .Fprintln (w , " quotes or escape those spaces, depending on the user's shell." )
60
- fmt .Fprintln (w )
61
- fmt .Fprintln (w , "Example:" )
62
- fmt .Fprintln (w , " sqlcup author/authors \" id:INTEGER:PRIMARY KEY\" \" name:text:NOT NULL\" bio:text" )
63
- fmt .Fprintln (w , " sqlcup --order-by name user/users \" id:INTEGER:PRIMARY KEY\" name:text" )
64
- fmt .Fprintln (w )
65
- fmt .Fprintln (w , "Options:" )
48
+ fmt .Fprintln (w , usage )
66
49
flag .PrintDefaults ()
67
50
}
68
51
@@ -106,9 +89,109 @@ type scaffoldCommandArgs struct {
106
89
}
107
90
108
91
func parseColumnDefinition (s string ) (column , error ) {
92
+ var (
93
+ plainColumn = strings .Contains (s , ":" )
94
+ smartColumn = strings .Contains (s , "#" )
95
+ )
96
+ if plainColumn && smartColumn {
97
+ return column {}, fmt .Errorf ("%w: invalid <column>: '%s' contains both plain and smart separators" , errBadArgument , s )
98
+ }
99
+ if plainColumn {
100
+ return parsePlainColumnDefinition (s )
101
+ } else if smartColumn {
102
+ return parseSmartColumnDefinition (s )
103
+ }
104
+ return column {}, fmt .Errorf ("%w: invalid <column>: '%s', expected <smart-column> or <plain-column>" , errBadArgument , s )
105
+ }
106
+
107
+ func parseSmartColumnDefinition (s string ) (column , error ) {
108
+ if s == "#id" {
109
+ return column {
110
+ ID : true ,
111
+ Name : "id" ,
112
+ Type : "INTEGER" ,
113
+ Constraint : "PRIMARY KEY" ,
114
+ }, nil
115
+ }
116
+
117
+ name , rest , _ := strings .Cut (s , "#" )
118
+ if name == "" {
119
+ return column {}, fmt .Errorf ("%w: '%s', missing <name>" , errInvalidSmartColumn , s )
120
+ }
121
+
122
+ var (
123
+ colType string
124
+ id bool
125
+ null bool
126
+ unique bool
127
+ )
128
+ tags := strings .Split (rest , "#" )
129
+ for _ , tag := range tags {
130
+ switch tag {
131
+ case "id" :
132
+ id = true
133
+ case "null" :
134
+ null = true
135
+ case "unique" :
136
+ unique = true
137
+ case "float" :
138
+ colType = "FLOAT"
139
+ case "double" :
140
+ colType = "DOUBLE"
141
+ case "datetime" :
142
+ colType = "DATETIME"
143
+ case "text" :
144
+ colType = "TEXT"
145
+ case "int" :
146
+ colType = "INTEGER"
147
+ case "blob" :
148
+ colType = "BLOB"
149
+ default :
150
+ return column {}, fmt .Errorf ("%w: '%s': unknown <tag> #%s" , errInvalidSmartColumn , s , tag )
151
+ }
152
+ }
153
+ if id {
154
+ if unique || null {
155
+ return column {}, fmt .Errorf ("%w: '%s', cannot combine #id with #unique or #null" , errInvalidSmartColumn , s )
156
+ }
157
+ if colType == "" {
158
+ colType = "INTEGER"
159
+ }
160
+ // sqlite special case
161
+ var constraint = "PRIMARY KEY"
162
+ if colType != "INTEGER" {
163
+ constraint = "NOT NULL " + constraint
164
+ }
165
+ return column {
166
+ Name : name ,
167
+ Type : colType ,
168
+ Constraint : constraint ,
169
+ ID : true ,
170
+ }, nil
171
+ }
172
+
173
+ if colType == "" {
174
+ return column {}, fmt .Errorf ("%w: '%s' missing column type" , errInvalidSmartColumn , s )
175
+ }
176
+ constraint := ""
177
+ if ! null {
178
+ constraint += " NOT NULL"
179
+ }
180
+ if unique {
181
+ constraint += " UNIQUE"
182
+ }
183
+ return column {
184
+ Name : name ,
185
+ Type : colType ,
186
+ Constraint : strings .TrimSpace (constraint ),
187
+ ID : false ,
188
+ }, nil
189
+ }
190
+
191
+ func parsePlainColumnDefinition (s string ) (column , error ) {
109
192
parts := strings .Split (s , ":" )
110
193
if len (parts ) < 2 || len (parts ) > 3 {
111
- return column {}, fmt .Errorf ("%w: invalid <column>: '%s', expected '<name>:<type>' or '<name>:<type>:<constraint>'" , errBadArgument , s )
194
+ return column {}, fmt .Errorf ("%w: invalid <plain- column>: '%s', expected '<name>:<type>' or '<name>:<type>:<constraint>'" , errBadArgument , s )
112
195
}
113
196
col := column {
114
197
ID : strings .ToLower (parts [0 ]) == * idColumnFlag ,
0 commit comments