Skip to content
Newer
Older
100644 315 lines (235 sloc) 7.5 KB
6e4a863 @lxn Add support for prepared statements
authored
1 // Copyright 2010 Alexander Neumann. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package pgsql
6
7 import (
8 "bytes"
9 "fmt"
10 "os"
11 "regexp"
12 )
13
14 var quoteRegExp = regexp.MustCompile("['][^']*[']")
15
16 // Statement is a means to efficiently execute a parameterized SQL command multiple times.
17 // Call *Conn.Prepare to create a new prepared Statement.
18 type Statement struct {
19 conn *Conn
20 name, portalName, command, actualCommand string
21 isClosed bool
22 params []*Parameter
23 name2param map[string]*Parameter
24 }
25
3072a33 @lxn Initial support for date/time types; expose runtime parameters; fix e…
authored
26 func replaceParameterNameInSubstring(s, old, new string, buf *bytes.Buffer, paramRegExp *regexp.Regexp) {
842388e @lxn Connect now uses a connection string instead of *ConnParams; fix bug …
authored
27 matchIndexPairs := paramRegExp.FindAllStringIndex(s, -1)
3072a33 @lxn Initial support for date/time types; expose runtime parameters; fix e…
authored
28 prevMatchEnd := 1
29
842388e @lxn Connect now uses a connection string instead of *ConnParams; fix bug …
authored
30 for _, pair := range matchIndexPairs {
31 matchStart := pair[0]
32 matchEnd := pair[1]
3072a33 @lxn Initial support for date/time types; expose runtime parameters; fix e…
authored
33
34 buf.WriteString(s[prevMatchEnd-1 : matchStart+1])
35 buf.WriteString(new)
36
37 prevMatchEnd = matchEnd
38 }
39
40 if prevMatchEnd > 1 {
41 buf.WriteString(s[prevMatchEnd-1:])
42 return
43 }
44
45 buf.WriteString(s)
46 }
47
6e4a863 @lxn Add support for prepared statements
authored
48 func replaceParameterName(command, old, new string) string {
3072a33 @lxn Initial support for date/time types; expose runtime parameters; fix e…
authored
49 paramRegExp := regexp.MustCompile("[\\- |\n\r\t,)(;=+/<>][:|@]" + old[1:] + "([\\- |\n\r\t,)(;=+/<>]|$)")
50
6e4a863 @lxn Add support for prepared statements
authored
51 buf := bytes.NewBuffer(nil)
52
842388e @lxn Connect now uses a connection string instead of *ConnParams; fix bug …
authored
53 quoteIndexPairs := quoteRegExp.FindAllStringIndex(command, -1)
6e4a863 @lxn Add support for prepared statements
authored
54 prevQuoteEnd := 0
3072a33 @lxn Initial support for date/time types; expose runtime parameters; fix e…
authored
55
842388e @lxn Connect now uses a connection string instead of *ConnParams; fix bug …
authored
56 for _, pair := range quoteIndexPairs {
57 quoteStart := pair[0]
58 quoteEnd := pair[1]
6e4a863 @lxn Add support for prepared statements
authored
59
3072a33 @lxn Initial support for date/time types; expose runtime parameters; fix e…
authored
60 replaceParameterNameInSubstring(command[prevQuoteEnd:quoteStart], old, new, buf, paramRegExp)
6e4a863 @lxn Add support for prepared statements
authored
61 buf.WriteString(command[quoteStart:quoteEnd])
62
63 prevQuoteEnd = quoteEnd
64 }
65
66 if buf.Len() > 0 {
3072a33 @lxn Initial support for date/time types; expose runtime parameters; fix e…
authored
67 replaceParameterNameInSubstring(command[prevQuoteEnd:], old, new, buf, paramRegExp)
6e4a863 @lxn Add support for prepared statements
authored
68
69 return buf.String()
70 }
71
3072a33 @lxn Initial support for date/time types; expose runtime parameters; fix e…
authored
72 replaceParameterNameInSubstring(command, old, new, buf, paramRegExp)
73
74 return buf.String()
6e4a863 @lxn Add support for prepared statements
authored
75 }
76
77 func adjustCommand(command string, params []*Parameter) string {
78 for i, p := range params {
2f122ab @lxn Add support for arbitrary types like enums in prepared statements
authored
79 var cast string
80 if p.customTypeName != "" {
81 cast = fmt.Sprintf("::%s", p.customTypeName)
82 }
83 command = replaceParameterName(command, p.name, fmt.Sprintf("$%d%s", i+1, cast))
6e4a863 @lxn Add support for prepared statements
authored
84 }
85
86 return command
87 }
88
89 func newStatement(conn *Conn, command string, params []*Parameter) *Statement {
90 if conn.LogLevel >= LogDebug {
91 defer conn.logExit(conn.logEnter("newStatement"))
92 }
93
ae3d6c7 @lxn Replace new(T) with &T{}
authored
94 stmt := &Statement{}
6e4a863 @lxn Add support for prepared statements
authored
95
96 stmt.name2param = make(map[string]*Parameter)
97
98 for _, param := range params {
ebe3081 @lxn Improve error message for *Parameter.SetValue
authored
99 if param == nil {
100 panic("received a nil parameter")
101 }
6e4a863 @lxn Add support for prepared statements
authored
102 if param.stmt != nil {
103 panic(fmt.Sprintf("parameter '%s' already used in another statement", param.name))
104 }
105 param.stmt = stmt
106
107 stmt.name2param[param.name] = param
108 }
109
110 stmt.conn = conn
111
374df0a @lxn Move statement and portal counters into Conn
authored
112 stmt.name = fmt.Sprint("stmt", conn.nextStatementId)
113 conn.nextStatementId++
6e4a863 @lxn Add support for prepared statements
authored
114
374df0a @lxn Move statement and portal counters into Conn
authored
115 stmt.portalName = fmt.Sprint("prtl", conn.nextPortalId)
116 conn.nextPortalId++
6e4a863 @lxn Add support for prepared statements
authored
117
118 stmt.command = command
119 stmt.actualCommand = adjustCommand(command, params)
120
121 stmt.params = make([]*Parameter, len(params))
122 copy(stmt.params, params)
123
124 return stmt
125 }
126
5c4a542 @lxn Add some parent object getters
authored
127 // Conn returns the *Conn this Statement is associated with.
128 func (stmt *Statement) Conn() *Conn {
129 return stmt.conn
130 }
131
6e4a863 @lxn Add support for prepared statements
authored
132 // Parameter returns the Parameter with the specified name or nil, if the Statement has no Parameter with that name.
133 func (stmt *Statement) Parameter(name string) *Parameter {
134 conn := stmt.conn
135
136 if conn.LogLevel >= LogVerbose {
137 defer conn.logExit(conn.logEnter("*Statement.Parameter"))
138 }
139
140 param, ok := stmt.name2param[name]
141 if !ok {
142 return nil
143 }
144
145 return param
146 }
147
148 // Parameters returns a slice containing the parameters of the Statement.
149 func (stmt *Statement) Parameters() []*Parameter {
150 conn := stmt.conn
151
152 if conn.LogLevel >= LogVerbose {
153 defer conn.logExit(conn.logEnter("*Statement.Parameters"))
154 }
155
156 params := make([]*Parameter, len(stmt.params))
157 copy(params, stmt.params)
158 return params
159 }
160
161 // IsClosed returns if the Statement has been closed.
162 func (stmt *Statement) IsClosed() bool {
163 conn := stmt.conn
164
165 if conn.LogLevel >= LogVerbose {
166 defer conn.logExit(conn.logEnter("*Statement.IsClosed"))
167 }
168
169 return stmt.isClosed
170 }
171
172 // Close closes the Statement, releasing resources on the server.
173 func (stmt *Statement) Close() (err os.Error) {
174 conn := stmt.conn
175
2646943 @lxn Fix func enter/exit logging
authored
176 if conn.LogLevel >= LogDebug {
177 defer conn.logExit(conn.logEnter("*Statement.Close"))
178 }
179
6e4a863 @lxn Add support for prepared statements
authored
180 defer func() {
181 if x := recover(); x != nil {
182 err = conn.logAndConvertPanic(x)
183 }
184 }()
185
a6c6879 @lxn Allow closing of statements and connections at any time
authored
186 stmt.conn.writeClose('S', stmt.name)
6e4a863 @lxn Add support for prepared statements
authored
187
188 stmt.isClosed = true
189 return
190 }
191
192 // ActualCommand returns the actual command text that is sent to the server.
193 // The original command is automatically adjusted if it contains parameters so
194 // it complies with what PostgreSQL expects. Refer to the return value of this
195 // method to make sense of the position information contained in many error
196 // messages.
197 func (stmt *Statement) ActualCommand() string {
198 conn := stmt.conn
199
200 if conn.LogLevel >= LogVerbose {
201 defer conn.logExit(conn.logEnter("*Statement.ActualCommand"))
202 }
203
204 return stmt.actualCommand
205 }
206
207 // Command is the original command text as given to *Conn.Prepare.
208 func (stmt *Statement) Command() string {
209 conn := stmt.conn
210
211 if conn.LogLevel >= LogVerbose {
212 defer conn.logExit(conn.logEnter("*Statement.Command"))
213 }
214
215 return stmt.command
216 }
217
218 // Query executes the Statement and returns a
82567f3 @lxn Rename Reader to ResultSet and ReadNext to FetchNext
authored
219 // ResultSet for row-by-row retrieval of the results.
220 // The returned ResultSet must be closed before sending another
6e4a863 @lxn Add support for prepared statements
authored
221 // query or command to the server over the same connection.
82567f3 @lxn Rename Reader to ResultSet and ReadNext to FetchNext
authored
222 func (stmt *Statement) Query() (res *ResultSet, err os.Error) {
6e4a863 @lxn Add support for prepared statements
authored
223 conn := stmt.conn
224
2646943 @lxn Fix func enter/exit logging
authored
225 if conn.LogLevel >= LogDebug {
226 defer conn.logExit(conn.logEnter("*Statement.Query"))
227 }
228
6e4a863 @lxn Add support for prepared statements
authored
229 defer func() {
230 if x := recover(); x != nil {
231 err = conn.logAndConvertPanic(x)
232 }
233 }()
234
b4a481e @lxn Add some more debug logging
authored
235 if conn.LogLevel >= LogCommand {
236 buf := bytes.NewBuffer(nil)
237
238 buf.WriteString("\n=================================================\n")
239
240 buf.WriteString("ActualCommand:\n")
241 buf.WriteString(stmt.actualCommand)
242 buf.WriteString("\n-------------------------------------------------\n")
243 buf.WriteString("Parameters:\n")
244
245 for i, p := range stmt.params {
246 buf.WriteString(fmt.Sprintf("$%d (%s) = '%v'\n", i+1, p.name, p.value))
247 }
248
249 buf.WriteString("=================================================\n")
250
251 conn.log(LogCommand, buf.String())
252 }
253
82567f3 @lxn Rename Reader to ResultSet and ReadNext to FetchNext
authored
254 r := newResultSet(conn)
6e4a863 @lxn Add support for prepared statements
authored
255
256 conn.state.execute(stmt, r)
257
82567f3 @lxn Rename Reader to ResultSet and ReadNext to FetchNext
authored
258 res = r
6e4a863 @lxn Add support for prepared statements
authored
259
260 return
261 }
262
263 // Execute executes the Statement and returns the number
264 // of rows affected. If the results of a query are needed, use the
265 // Query method instead.
266 func (stmt *Statement) Execute() (rowsAffected int64, err os.Error) {
267 conn := stmt.conn
268
2646943 @lxn Fix func enter/exit logging
authored
269 if conn.LogLevel >= LogDebug {
270 defer conn.logExit(conn.logEnter("*Statement.Execute"))
271 }
272
6e4a863 @lxn Add support for prepared statements
authored
273 defer func() {
274 if x := recover(); x != nil {
275 err = conn.logAndConvertPanic(x)
276 }
277 }()
278
82567f3 @lxn Rename Reader to ResultSet and ReadNext to FetchNext
authored
279 res, err := stmt.Query()
6e4a863 @lxn Add support for prepared statements
authored
280 if err != nil {
281 return
282 }
283
82567f3 @lxn Rename Reader to ResultSet and ReadNext to FetchNext
authored
284 err = res.Close()
6e4a863 @lxn Add support for prepared statements
authored
285
82567f3 @lxn Rename Reader to ResultSet and ReadNext to FetchNext
authored
286 rowsAffected = res.rowsAffected
6e4a863 @lxn Add support for prepared statements
authored
287 return
288 }
6435df4 @lxn Add Scan methods to reduce error handling noise
authored
289
290 // Scan executes the statement and scans the fields of the first row
291 // in the ResultSet, trying to store field values into the specified
292 // arguments. The arguments must be of pointer types. If a row has
293 // been fetched, fetched will be true, otherwise false.
294 func (stmt *Statement) Scan(args ...interface{}) (fetched bool, err os.Error) {
295 conn := stmt.conn
296
297 if conn.LogLevel >= LogDebug {
298 defer conn.logExit(conn.logEnter("*Statement.Scan"))
299 }
300
301 defer func() {
302 if x := recover(); x != nil {
303 err = conn.logAndConvertPanic(x)
304 }
305 }()
306
49d2cbf @lxn gofmt
authored
307 res, err := stmt.Query()
308 if err != nil {
309 return
310 }
311 defer res.Close()
6435df4 @lxn Add Scan methods to reduce error handling noise
authored
312
d536681 @lxn Adjust to release 2010-09-29 variadic func changes
authored
313 return res.ScanNext(args...)
6435df4 @lxn Add Scan methods to reduce error handling noise
authored
314 }
Something went wrong with that request. Please try again.