You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Use the `#active?` method to determine if a connection is good. The implementation of this method may change but it should always guarantee that a connection is good. Current it checks for either a closed or dead connection.
@@ -147,169 +147,99 @@ Send a SQL string to the database and return a TinyTds::Result object.
147
147
result = client.execute("SELECT * FROM [datatypes]")
148
148
```
149
149
150
+
## Sending queries and receiving results
150
151
151
-
## TinyTds::Result Usage
152
+
The client implements three different methods to send queries to a SQL server.
152
153
153
-
A result object is returned by the client's execute command. It is important that you either return the data from the query, most likely with the #each method, or that you cancel the results before asking the client to execute another SQL batch. Failing to do so will yield an error.
154
-
155
-
Calling #each on the result will lazily load each row from the database.
154
+
`client.insert` will execute the query and return the last identifier.
156
155
157
156
```ruby
158
-
result.each do |row|
159
-
# By default each row is a hash.
160
-
# The keys are the fields, as you'd expect.
161
-
# The values are pre-built Ruby primitives mapped from their corresponding types.
162
-
end
157
+
client.insert("INSERT INTO [datatypes] ([varchar_50]) VALUES ('text')")
158
+
# => 363
163
159
```
164
160
165
-
A result object has a `#fields` accessor. It can be called before the result rows are iterated over. Even if no rows are returned, #fields will still return the column names you expected. Any SQL that does not return columned data will always return an empty array for `#fields`. It is important to remember that if you access the `#fields` before iterating over the results, the columns will always follow the default query option's `:symbolize_keys` setting at the client's level and will ignore the query options passed to each.
161
+
`client.do`will execute the query and tell you how many rows were affected.
166
162
167
163
```ruby
168
-
result = client.execute("USE [tinytdstest]")
169
-
result.fields # => []
170
-
result.do
171
-
172
-
result = client.execute("SELECT [id] FROM [datatypes]")
173
-
result.fields # => ["id"]
174
-
result.cancel
175
-
result = client.execute("SELECT [id] FROM [datatypes]")
176
-
result.each(:symbolize_keys => true)
177
-
result.fields # => [:id]
164
+
client.do("DELETE FROM [datatypes] WHERE [varchar_50] = 'text'")
165
+
# 1
178
166
```
179
167
180
-
You can cancel a result object's data from being loading by the server.
168
+
Both `do` and `insert` will not serialize any results sent by the SQL server, making them extremely fast and memory-efficient for large operations.
169
+
170
+
`client.execute` will execute the query and return you a `TinyTds::Result` object.
181
171
182
172
```ruby
183
-
result = client.execute("SELECT * FROM [super_big_table]")
184
-
result.cancel
173
+
client.execute("SELECT [id] FROM [datatypes]")
174
+
# =>
175
+
# #<TinyTds::Result:0x000057d6275ce3b0
176
+
# @fields=["id"],
177
+
# @return_code=nil,
178
+
# @rows=
179
+
# [{"id"=>11},
180
+
# {"id"=>12},
181
+
# {"id"=>21},
182
+
# {"id"=>31},
185
183
```
186
184
187
-
You can use results cancelation in conjunction with results lazy loading, no problem.
185
+
A result object has a `fields` accessor. Even if no rows are returned, `fields` will still return the column names you expected. Any SQL that does not return columned data will always return an empty array for `fields`.
188
186
189
187
```ruby
190
-
result = client.execute("SELECT * FROM [super_big_table]")
191
-
result.each_with_index do |row, i|
192
-
breakif row >10
193
-
end
194
-
result.cancel
188
+
result = client.execute("USE [tinytdstest]")
189
+
result.fields # => []
190
+
191
+
result = client.execute("SELECT [id] FROM [datatypes]")
192
+
result.fields # => ["id"]
195
193
```
196
194
197
-
If the SQL executed by the client returns affected rows, you can easily find out how many.
195
+
You can retrieve the results by accessing the `rows` property on the result.
198
196
199
197
```ruby
200
-
result.each
201
-
result.affected_rows # => 24
198
+
result.rows
199
+
# =>
200
+
# [{"id"=>11},
201
+
# {"id"=>12},
202
+
# {"id"=>21},
203
+
# ...
202
204
```
203
205
204
-
This pattern is so common for UPDATE and DELETE statements that the #do method cancels any need for loading the result data and returns the `#affected_rows`.
206
+
The result object also has `affected_rows`, which usually also corresponds to the length of items in `rows`. But if you execute a `DELETE` statement with `execute, `rows` is likely empty but `affected_rows` will still list a couple of items.
205
207
206
208
```ruby
207
209
result = client.execute("DELETE FROM [datatypes]")
Likewise for `INSERT` statements, the #insert method cancels any need for loading the result data and executes a `SCOPE_IDENTITY()` for the primary key.
212
-
213
-
```ruby
214
-
result = client.execute("INSERT INTO [datatypes] ([xml]) VALUES ('<html><br/></html>')")
215
-
result.insert # => 420
216
-
```
217
+
But as mentioned earlier, best use `do` when you are only interested in the `affected_rows`.
217
218
218
-
The result object can handle multiple result sets form batched SQL or stored procedures. It is critical to remember that when calling each with a block for the first time will return each "row" of each result set. Calling each a second time with a block will yield each "set".
219
+
The result object can handle multiple result sets form batched SQL or stored procedures.
219
220
220
221
```ruby
221
222
sql = ["SELECT TOP (1) [id] FROM [datatypes]",
222
223
"SELECT TOP (2) [bigint] FROM [datatypes] WHERE [bigint] IS NOT NULL"].join('')
Use the `#sqlsent?` and `#canceled?` query methods on the client to determine if an active SQL batch still needs to be processed and or if data results were canceled from the last result object. These values reset to true and false respectively for the client at the start of each `#execute` and new result object. Or if all rows are processed normally, `#sqlsent?` will return false. To demonstrate, lets assume we have 100 rows in the result object.
245
-
246
-
```ruby
247
-
client.sqlsent? # = false
248
-
client.canceled? # = false
249
-
250
-
result = client.execute("SELECT * FROM [super_big_table]")
251
-
252
-
client.sqlsent? # = true
253
-
client.canceled? # = false
254
-
255
-
result.each do |row|
256
-
# Assume we break after 20 rows with 80 still pending.
257
-
breakif row["id"] >20
258
-
end
259
-
260
-
client.sqlsent? # = true
261
-
client.canceled? # = false
262
-
263
-
result.cancel
264
-
265
-
client.sqlsent? # = false
266
-
client.canceled? # = true
267
-
```
268
-
269
-
It is possible to get the return code after executing a stored procedure from either the result or client object.
270
-
271
-
```ruby
272
-
client.return_code # => nil
273
-
274
-
result = client.execute("EXEC tinytds_TestReturnCodes")
275
-
result.do
276
-
result.return_code # => 420
277
-
client.return_code # => 420
278
228
```
279
229
280
-
281
230
## Query Options
282
231
283
-
Every `TinyTds::Result` object can pass query options to the #each method. The defaults are defined and configurable by setting options in the `TinyTds::Client.default_query_options` hash. The default values are:
284
-
285
-
*:as => :hash - Object for each row yielded. Can be set to :array.
*:cache_rows => true - Successive calls to #each returns the cached rows.
288
-
*:timezone => :local - Local to the Ruby client or :utc for UTC.
289
-
*:empty_sets => true - Include empty results set in queries that return multiple result sets.
232
+
You can pass query options to `execute`. The defaults are defined and configurable by setting options in the `TinyTds::Client.default_query_options` hash. The default values are:
290
233
291
-
Each result gets a copy of the default options you specify at the client level and can be overridden by passing an options hash to the #each method. For example
234
+
*`as: :hash` - Object for each row yielded. Can be set to :array.
235
+
*`empty_sets: true` - Include empty results set in queries that return multiple result sets.
236
+
*`timezone: :local` - Local to the Ruby client or :utc for UTC.
292
237
293
238
```ruby
294
-
result.each(:as => :array, :cache_rows => false) do |row|
295
-
# Each row is now an array of values ordered by #fields.
296
-
# Rows are yielded and forgotten about, freeing memory.
297
-
end
239
+
result = client.execute("SELECT [datetime2_2] FROM [datatypes] WHERE [id] = 74", as::array, timezone::utc, empty_sets:true)
Besides the standard query options, the result object can take one additional option. Using `:first => true` will only load the first row of data and cancel all remaining results.
301
-
302
-
```ruby
303
-
result = client.execute("SELECT * FROM [super_big_table]")
304
-
result.each(:first => true) # => [{'id' => 24}]
305
-
```
306
-
307
-
308
-
## Row Caching
309
-
310
-
By default row caching is turned on because the SQL Server adapter for ActiveRecord would not work without it. I hope to find some time to create some performance patches for ActiveRecord that would allow it to take advantages of lazily created yielded rows from result objects. Currently only TinyTDS and the Mysql2 gem allow such a performance gain.
311
-
312
-
313
243
## Encoding Error Handling
314
244
315
245
TinyTDS takes an opinionated stance on how we handle encoding errors. First, we treat errors differently on reads vs. writes. Our opinion is that if you are reading bad data due to your client's encoding option, you would rather just find `?` marks in your strings vs being blocked with exceptions. This is how things wold work via ODBC or SMS. On the other hand, writes will raise an exception. In this case we raise the SYBEICONVO/2402 error message which has a description of `Error converting characters into server's character set. Some character(s) could not be converted.`. Even though the severity of this message is only a `4` and TinyTDS will automatically strip/ignore unknown characters, we feel you should know that you are inserting bad encodings. In this way, a transaction can be rolled back, etc. Remember, any database write that has bad characters due to the client encoding will still be written to the database, but it is up to you rollback said write if needed. Most ORMs like ActiveRecord handle this scenario just fine.
0 commit comments