rubyfb 0.5.2-x86-mswin32-60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +6 -0
- data/LICENSE +411 -0
- data/Manifest +75 -0
- data/README +460 -0
- data/Rakefile +21 -0
- data/examples/example01.rb +65 -0
- data/ext/AddUser.c +464 -0
- data/ext/AddUser.h +37 -0
- data/ext/Backup.c +783 -0
- data/ext/Backup.h +37 -0
- data/ext/Blob.c +421 -0
- data/ext/Blob.h +65 -0
- data/ext/Common.c +54 -0
- data/ext/Common.h +37 -0
- data/ext/Connection.c +863 -0
- data/ext/Connection.h +50 -0
- data/ext/DataArea.c +274 -0
- data/ext/DataArea.h +38 -0
- data/ext/Database.c +449 -0
- data/ext/Database.h +48 -0
- data/ext/FireRuby.c +240 -0
- data/ext/FireRuby.h +50 -0
- data/ext/FireRubyException.c +268 -0
- data/ext/FireRubyException.h +51 -0
- data/ext/Generator.c +689 -0
- data/ext/Generator.h +53 -0
- data/ext/RemoveUser.c +212 -0
- data/ext/RemoveUser.h +37 -0
- data/ext/Restore.c +855 -0
- data/ext/Restore.h +37 -0
- data/ext/ResultSet.c +810 -0
- data/ext/ResultSet.h +60 -0
- data/ext/Row.c +965 -0
- data/ext/Row.h +55 -0
- data/ext/ServiceManager.c +316 -0
- data/ext/ServiceManager.h +48 -0
- data/ext/Services.c +124 -0
- data/ext/Services.h +42 -0
- data/ext/Statement.c +785 -0
- data/ext/Statement.h +62 -0
- data/ext/Transaction.c +684 -0
- data/ext/Transaction.h +50 -0
- data/ext/TypeMap.c +1182 -0
- data/ext/TypeMap.h +51 -0
- data/ext/extconf.rb +30 -0
- data/lib/SQLType.rb +224 -0
- data/lib/active_record/connection_adapters/rubyfb_adapter.rb +805 -0
- data/lib/mkdoc +1 -0
- data/lib/rubyfb.rb +2 -0
- data/lib/rubyfb_lib.so +0 -0
- data/lib/src.rb +1800 -0
- data/mswin32fb/fbclient_ms.lib +0 -0
- data/mswin32fb/ibase.h +2555 -0
- data/mswin32fb/iberror.h +1741 -0
- data/rubyfb.gemspec +31 -0
- data/test/AddRemoveUserTest.rb +56 -0
- data/test/BackupRestoreTest.rb +99 -0
- data/test/BlobTest.rb +57 -0
- data/test/CharacterSetTest.rb +63 -0
- data/test/ConnectionTest.rb +111 -0
- data/test/DDLTest.rb +54 -0
- data/test/DatabaseTest.rb +83 -0
- data/test/GeneratorTest.rb +50 -0
- data/test/KeyTest.rb +140 -0
- data/test/ResultSetTest.rb +162 -0
- data/test/RoleTest.rb +73 -0
- data/test/RowCountTest.rb +65 -0
- data/test/RowTest.rb +203 -0
- data/test/SQLTest.rb +182 -0
- data/test/SQLTypeTest.rb +101 -0
- data/test/ServiceManagerTest.rb +29 -0
- data/test/StatementTest.rb +135 -0
- data/test/TestSetup.rb +11 -0
- data/test/TransactionTest.rb +112 -0
- data/test/TypeTest.rb +92 -0
- data/test/UnitTest.rb +65 -0
- metadata +143 -0
data/ext/Blob.h
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
/*------------------------------------------------------------------------------
|
2
|
+
* Blob.h
|
3
|
+
*----------------------------------------------------------------------------*/
|
4
|
+
/**
|
5
|
+
* Copyright � Peter Wood, 2005
|
6
|
+
*
|
7
|
+
* The contents of this file are subject to the Mozilla Public License Version
|
8
|
+
* 1.1 (the "License"); you may not use this file except in compliance with the
|
9
|
+
* License. You may obtain a copy of the License at
|
10
|
+
*
|
11
|
+
* http://www.mozilla.org/MPL/
|
12
|
+
*
|
13
|
+
* Software distributed under the License is distributed on an "AS IS" basis,
|
14
|
+
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
15
|
+
* the specificlanguage governing rights and limitations under the License.
|
16
|
+
*
|
17
|
+
* The Original Code is the FireRuby extension for the Ruby language.
|
18
|
+
*
|
19
|
+
* The Initial Developer of the Original Code is Peter Wood. All Rights
|
20
|
+
* Reserved.
|
21
|
+
*
|
22
|
+
* @author Peter Wood
|
23
|
+
* @version 1.0
|
24
|
+
*/
|
25
|
+
#ifndef FIRERUBY_BLOB_H
|
26
|
+
#define FIRERUBY_BLOB_H
|
27
|
+
|
28
|
+
/* Includes. */
|
29
|
+
#ifndef FIRERUBY_FIRE_RUBY_EXCEPTION_H
|
30
|
+
#include "FireRubyException.h"
|
31
|
+
#endif
|
32
|
+
|
33
|
+
#ifndef IBASE_H_INCLUDED
|
34
|
+
#include "ibase.h"
|
35
|
+
#define IBASE_H_INCLUDED
|
36
|
+
#endif
|
37
|
+
|
38
|
+
#ifndef RUBY_H_INCLUDED
|
39
|
+
#include "ruby.h"
|
40
|
+
#define RUBY_H_INCLUDED
|
41
|
+
#endif
|
42
|
+
|
43
|
+
/* Type definitions. */
|
44
|
+
typedef struct
|
45
|
+
{
|
46
|
+
ISC_BLOB_DESC description;
|
47
|
+
ISC_LONG segments,
|
48
|
+
size;
|
49
|
+
isc_blob_handle handle;
|
50
|
+
} BlobHandle;
|
51
|
+
|
52
|
+
/* Data elements. */
|
53
|
+
extern VALUE cBlob;
|
54
|
+
|
55
|
+
/* Function prototypes. */
|
56
|
+
BlobHandle *openBlob(ISC_QUAD,
|
57
|
+
char *,
|
58
|
+
char *,
|
59
|
+
isc_db_handle *,
|
60
|
+
isc_tr_handle *);
|
61
|
+
void Init_Blob(VALUE);
|
62
|
+
void blobFree(void *);
|
63
|
+
VALUE initializeBlob(VALUE);
|
64
|
+
|
65
|
+
#endif /* FIRERUBY_BLOB_H */
|
data/ext/Common.c
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
/*------------------------------------------------------------------------------
|
2
|
+
* Common.c
|
3
|
+
*----------------------------------------------------------------------------*/
|
4
|
+
/**
|
5
|
+
* Copyright � Peter Wood, 2005
|
6
|
+
*
|
7
|
+
* The contents of this file are subject to the Mozilla Public License Version
|
8
|
+
* 1.1 (the "License"); you may not use this file except in compliance with the
|
9
|
+
* License. You may obtain a copy of the License at
|
10
|
+
*
|
11
|
+
* http://www.mozilla.org/MPL/
|
12
|
+
*
|
13
|
+
* Software distributed under the License is distributed on an "AS IS" basis,
|
14
|
+
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
15
|
+
* the specificlanguage governing rights and limitations under the License.
|
16
|
+
*
|
17
|
+
* The Original Code is the FireRuby extension for the Ruby language.
|
18
|
+
*
|
19
|
+
* The Initial Developer of the Original Code is Peter Wood. All Rights
|
20
|
+
* Reserved.
|
21
|
+
*
|
22
|
+
* @author Peter Wood
|
23
|
+
* @version 1.0
|
24
|
+
*/
|
25
|
+
|
26
|
+
/* Includes. */
|
27
|
+
#include "Common.h"
|
28
|
+
#include "FireRubyException.h"
|
29
|
+
|
30
|
+
|
31
|
+
/**
|
32
|
+
* This function is used to preclude the use of any of the Ruby copy methods
|
33
|
+
* (clone or dup) with an object.
|
34
|
+
*
|
35
|
+
* @param copy A reference to the object copy.
|
36
|
+
* @param original A reference to the original object that is being copied.
|
37
|
+
*
|
38
|
+
* @return Never returns as an exception is always generated.
|
39
|
+
*
|
40
|
+
*/
|
41
|
+
VALUE forbidObjectCopy(VALUE copy, VALUE original)
|
42
|
+
{
|
43
|
+
VALUE message = rb_str_new2("Copying of CLASS_NAME objects is forbidden."),
|
44
|
+
value = rb_funcall(original, rb_intern("class"), 0),
|
45
|
+
array[2];
|
46
|
+
|
47
|
+
array[0] = rb_str_new2("CLASS_NAME");
|
48
|
+
array[1] = rb_funcall(value, rb_intern("name"), 0);
|
49
|
+
message = rb_funcall(message, rb_intern("gsub"), 2, array);
|
50
|
+
|
51
|
+
rb_fireruby_raise(NULL, STR2CSTR(message));
|
52
|
+
|
53
|
+
return(Qnil);
|
54
|
+
}
|
data/ext/Common.h
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
/*------------------------------------------------------------------------------
|
2
|
+
* Common.h
|
3
|
+
*----------------------------------------------------------------------------*/
|
4
|
+
/**
|
5
|
+
* Copyright � Peter Wood, 2005
|
6
|
+
*
|
7
|
+
* The contents of this file are subject to the Mozilla Public License Version
|
8
|
+
* 1.1 (the "License"); you may not use this file except in compliance with the
|
9
|
+
* License. You may obtain a copy of the License at
|
10
|
+
*
|
11
|
+
* http://www.mozilla.org/MPL/
|
12
|
+
*
|
13
|
+
* Software distributed under the License is distributed on an "AS IS" basis,
|
14
|
+
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
15
|
+
* the specificlanguage governing rights and limitations under the License.
|
16
|
+
*
|
17
|
+
* The Original Code is the FireRuby extension for the Ruby language.
|
18
|
+
*
|
19
|
+
* The Initial Developer of the Original Code is Peter Wood. All Rights
|
20
|
+
* Reserved.
|
21
|
+
*
|
22
|
+
* @author Peter Wood
|
23
|
+
* @version 1.0
|
24
|
+
*/
|
25
|
+
#ifndef FIRERUBY_COMMON_H
|
26
|
+
#define FIRERUBY_COMMON_H
|
27
|
+
|
28
|
+
/* Includes. */
|
29
|
+
#ifndef RUBY_H_INCLUDED
|
30
|
+
#include "ruby.h"
|
31
|
+
#define RUBY_H_INCLUDED
|
32
|
+
#endif
|
33
|
+
|
34
|
+
/* Function prototypes. */
|
35
|
+
VALUE forbidObjectCopy(VALUE, VALUE);
|
36
|
+
|
37
|
+
#endif /* FIRERUBY_COMMON_H */
|
data/ext/Connection.c
ADDED
@@ -0,0 +1,863 @@
|
|
1
|
+
/*------------------------------------------------------------------------------
|
2
|
+
* Connection.c
|
3
|
+
*----------------------------------------------------------------------------*/
|
4
|
+
/**
|
5
|
+
* Copyright � Peter Wood, 2005
|
6
|
+
*
|
7
|
+
* The contents of this file are subject to the Mozilla Public License Version
|
8
|
+
* 1.1 (the "License"); you may not use this file except in compliance with the
|
9
|
+
* License. You may obtain a copy of the License at
|
10
|
+
*
|
11
|
+
* http://www.mozilla.org/MPL/
|
12
|
+
*
|
13
|
+
* Software distributed under the License is distributed on an "AS IS" basis,
|
14
|
+
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
15
|
+
* the specificlanguage governing rights and limitations under the License.
|
16
|
+
*
|
17
|
+
* The Original Code is the FireRuby extension for the Ruby language.
|
18
|
+
*
|
19
|
+
* The Initial Developer of the Original Code is Peter Wood. All Rights
|
20
|
+
* Reserved.
|
21
|
+
*
|
22
|
+
* @author Peter Wood
|
23
|
+
* @version 1.0
|
24
|
+
*/
|
25
|
+
|
26
|
+
/* Includes. */
|
27
|
+
#include "Connection.h"
|
28
|
+
#include "Database.h"
|
29
|
+
#include "ResultSet.h"
|
30
|
+
#include "Statement.h"
|
31
|
+
#include "Transaction.h"
|
32
|
+
#include "Common.h"
|
33
|
+
|
34
|
+
/* Function prototypes. */
|
35
|
+
static VALUE allocateConnection(VALUE);
|
36
|
+
static VALUE initializeConnection(int, VALUE *, VALUE);
|
37
|
+
static VALUE isConnectionOpen(VALUE);
|
38
|
+
static VALUE isConnectionClosed(VALUE);
|
39
|
+
static VALUE closeConnection(VALUE);
|
40
|
+
static VALUE getConnectionDatabase(VALUE);
|
41
|
+
static VALUE startConnectionTransaction(VALUE);
|
42
|
+
static VALUE connectionToString(VALUE);
|
43
|
+
static VALUE executeOnConnection(VALUE, VALUE, VALUE);
|
44
|
+
static VALUE executeOnConnectionImmediate(VALUE, VALUE);
|
45
|
+
static VALUE getConnectionUser(VALUE);
|
46
|
+
VALUE startTransactionBlock(VALUE);
|
47
|
+
VALUE startTransactionRescue(VALUE, VALUE);
|
48
|
+
VALUE executeBlock(VALUE);
|
49
|
+
VALUE executeRescue(VALUE, VALUE);
|
50
|
+
VALUE executeImmediateBlock(VALUE);
|
51
|
+
VALUE executeImmediateRescue(VALUE, VALUE);
|
52
|
+
char *createDPB(VALUE, VALUE, VALUE, short *);
|
53
|
+
|
54
|
+
/* Globals. */
|
55
|
+
VALUE cConnection;
|
56
|
+
|
57
|
+
|
58
|
+
/**
|
59
|
+
* This function provides the allocation functionality for the Connection
|
60
|
+
* class.
|
61
|
+
*
|
62
|
+
* @param klass A reference to the Connection Class object.
|
63
|
+
*
|
64
|
+
* @return A reference to the newly created instance.
|
65
|
+
*
|
66
|
+
*/
|
67
|
+
static VALUE allocateConnection(VALUE klass)
|
68
|
+
{
|
69
|
+
VALUE instance = Qnil;
|
70
|
+
ConnectionHandle *connection = ALLOC(ConnectionHandle);
|
71
|
+
|
72
|
+
if(connection != NULL)
|
73
|
+
{
|
74
|
+
/* Wrap the structure in a class. */
|
75
|
+
connection->handle = 0;
|
76
|
+
instance = Data_Wrap_Struct(klass, NULL, connectionFree, connection);
|
77
|
+
}
|
78
|
+
else
|
79
|
+
{
|
80
|
+
rb_raise(rb_eNoMemError,
|
81
|
+
"Memory allocation failure creating a connection.");
|
82
|
+
}
|
83
|
+
|
84
|
+
return(instance);
|
85
|
+
}
|
86
|
+
|
87
|
+
|
88
|
+
/**
|
89
|
+
* This function provides the initialize method for the Connection class.
|
90
|
+
*
|
91
|
+
* @param argc A count of the total number of arguments passed to the
|
92
|
+
* function.
|
93
|
+
* @param argv A pointer to an array of VALUEs that contain the arguments
|
94
|
+
* to the function.
|
95
|
+
* @param self A reference to the object being initialized.
|
96
|
+
*
|
97
|
+
* @return A reference to the initialized object.
|
98
|
+
*
|
99
|
+
*/
|
100
|
+
static VALUE initializeConnection(int argc, VALUE *argv, VALUE self)
|
101
|
+
{
|
102
|
+
ConnectionHandle *connection = NULL;
|
103
|
+
ISC_STATUS status[20];
|
104
|
+
short length = 0;
|
105
|
+
char *file = NULL,
|
106
|
+
*dpb = NULL;
|
107
|
+
VALUE user = Qnil,
|
108
|
+
password = Qnil,
|
109
|
+
options = Qnil;
|
110
|
+
|
111
|
+
if(argc < 1)
|
112
|
+
{
|
113
|
+
rb_raise(rb_eArgError, "Wrong number of arguments (%d for %d).", argc, 1);
|
114
|
+
}
|
115
|
+
|
116
|
+
if(TYPE(argv[0]) != T_DATA ||
|
117
|
+
RDATA(argv[0])->dfree != (RUBY_DATA_FUNC)databaseFree)
|
118
|
+
{
|
119
|
+
rb_fireruby_raise(NULL, "Invalid database specified for connection.");
|
120
|
+
}
|
121
|
+
file = STR2CSTR(rb_iv_get(argv[0], "@file"));
|
122
|
+
Data_Get_Struct(self, ConnectionHandle, connection);
|
123
|
+
|
124
|
+
/* Extract parameters. */
|
125
|
+
if(argc > 1)
|
126
|
+
{
|
127
|
+
user = argv[1];
|
128
|
+
}
|
129
|
+
if(argc > 2)
|
130
|
+
{
|
131
|
+
password = argv[2];
|
132
|
+
}
|
133
|
+
if(argc > 3)
|
134
|
+
{
|
135
|
+
options = argv[3];
|
136
|
+
}
|
137
|
+
|
138
|
+
/* Open the connection connection. */
|
139
|
+
dpb = createDPB(user, password, options, &length);
|
140
|
+
if(isc_attach_database(status, strlen(file), file, &connection->handle,
|
141
|
+
length, dpb) != 0)
|
142
|
+
{
|
143
|
+
/* Generate an error. */
|
144
|
+
free(dpb);
|
145
|
+
rb_fireruby_raise(status, "Error opening database connection.");
|
146
|
+
}
|
147
|
+
free(dpb);
|
148
|
+
|
149
|
+
/* Store connection attributes. */
|
150
|
+
rb_iv_set(self, "@database", argv[0]);
|
151
|
+
rb_iv_set(self, "@user", user);
|
152
|
+
rb_iv_set(self, "@transactions", rb_ary_new());
|
153
|
+
|
154
|
+
return(self);
|
155
|
+
}
|
156
|
+
|
157
|
+
|
158
|
+
/**
|
159
|
+
* This function provides the open? method for the Connection class.
|
160
|
+
*
|
161
|
+
* @param self A reference to the object that the call is being made on.
|
162
|
+
*
|
163
|
+
* @return Qtrue if the connection is open, Qfalse if it is closed.
|
164
|
+
*
|
165
|
+
*/
|
166
|
+
static VALUE isConnectionOpen(VALUE self)
|
167
|
+
{
|
168
|
+
VALUE result = Qfalse;
|
169
|
+
ConnectionHandle *connection = NULL;
|
170
|
+
|
171
|
+
Data_Get_Struct(self, ConnectionHandle, connection);
|
172
|
+
if(connection->handle != 0)
|
173
|
+
{
|
174
|
+
result = Qtrue;
|
175
|
+
}
|
176
|
+
|
177
|
+
return(result);
|
178
|
+
}
|
179
|
+
|
180
|
+
|
181
|
+
/**
|
182
|
+
* This function provides the closed? method for the Connection class.
|
183
|
+
*
|
184
|
+
* @param self A reference to the object that the call is being made on.
|
185
|
+
*
|
186
|
+
* @return Qtrue if the connection is closed, Qfalse if it is open.
|
187
|
+
*
|
188
|
+
*/
|
189
|
+
static VALUE isConnectionClosed(VALUE self)
|
190
|
+
{
|
191
|
+
return(isConnectionOpen(self) == Qtrue ? Qfalse : Qtrue);
|
192
|
+
}
|
193
|
+
|
194
|
+
|
195
|
+
/**
|
196
|
+
* This method provides the close method for the Connection class.
|
197
|
+
*
|
198
|
+
* @param self A reference to the object that the call is being made on.
|
199
|
+
*
|
200
|
+
* @return A reference to the closed Connection on success, nil otherwise or
|
201
|
+
* if the method is called on a closed Connection.
|
202
|
+
*
|
203
|
+
*/
|
204
|
+
static VALUE closeConnection(VALUE self)
|
205
|
+
{
|
206
|
+
VALUE result = Qnil;
|
207
|
+
ConnectionHandle *connection = NULL;
|
208
|
+
|
209
|
+
Data_Get_Struct(self, ConnectionHandle, connection);
|
210
|
+
if(connection->handle != 0)
|
211
|
+
{
|
212
|
+
VALUE transactions = rb_iv_get(self, "@transactions"),
|
213
|
+
transaction = Qnil;
|
214
|
+
ISC_STATUS status[20];
|
215
|
+
|
216
|
+
/* Roll back an outstanding transactions. */
|
217
|
+
while((transaction = rb_ary_pop(transactions)) != Qnil)
|
218
|
+
{
|
219
|
+
VALUE active = rb_funcall(transaction, rb_intern("active?"), 0);
|
220
|
+
|
221
|
+
if(active == Qtrue)
|
222
|
+
{
|
223
|
+
rb_funcall(transaction, rb_intern("rollback"), 0);
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
/* Detach from the database. */
|
228
|
+
if(isc_detach_database(status, &connection->handle) == 0)
|
229
|
+
{
|
230
|
+
connection->handle = 0;
|
231
|
+
result = self;
|
232
|
+
}
|
233
|
+
else
|
234
|
+
{
|
235
|
+
/* Generate an error. */
|
236
|
+
rb_fireruby_raise(status, "Error closing connection.");
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
return(result);
|
241
|
+
}
|
242
|
+
|
243
|
+
|
244
|
+
/**
|
245
|
+
* This function retrieves the connection associated with a Connection object.
|
246
|
+
*
|
247
|
+
* @param self A reference to the object that the call is being made on.
|
248
|
+
*
|
249
|
+
* @return A reference to the Connection connection.
|
250
|
+
*
|
251
|
+
*/
|
252
|
+
static VALUE getConnectionDatabase(VALUE self)
|
253
|
+
{
|
254
|
+
return(rb_iv_get(self, "@database"));
|
255
|
+
}
|
256
|
+
|
257
|
+
|
258
|
+
/**
|
259
|
+
* This function provides the start_transaction method for the Database class.
|
260
|
+
*
|
261
|
+
* @param self A reference to the Database object to start the transaction
|
262
|
+
* on.
|
263
|
+
*
|
264
|
+
* @return A reference to a Transaction object or nil if a problem occurs.
|
265
|
+
*
|
266
|
+
*/
|
267
|
+
static VALUE startConnectionTransaction(VALUE self)
|
268
|
+
{
|
269
|
+
VALUE result = rb_transaction_new(self);
|
270
|
+
|
271
|
+
if(rb_block_given_p())
|
272
|
+
{
|
273
|
+
result = rb_rescue(startTransactionBlock, result,
|
274
|
+
startTransactionRescue, result);
|
275
|
+
}
|
276
|
+
|
277
|
+
return(result);
|
278
|
+
}
|
279
|
+
|
280
|
+
|
281
|
+
/**
|
282
|
+
* This method provides the to_s method for the Connection class.
|
283
|
+
*
|
284
|
+
* @param self A reference to the Connection object that the method will be
|
285
|
+
* called on.
|
286
|
+
*
|
287
|
+
* @return A reference to a String object describing the connection.
|
288
|
+
*
|
289
|
+
*/
|
290
|
+
static VALUE connectionToString(VALUE self)
|
291
|
+
{
|
292
|
+
VALUE result = rb_str_new2("(CLOSED)");
|
293
|
+
ConnectionHandle *connection = NULL;
|
294
|
+
|
295
|
+
Data_Get_Struct(self, ConnectionHandle, connection);
|
296
|
+
if(connection->handle != 0)
|
297
|
+
{
|
298
|
+
VALUE database = rb_iv_get(self, "@database"),
|
299
|
+
user = rb_iv_get(self, "@user"),
|
300
|
+
file = rb_iv_get(database, "@file");
|
301
|
+
char text[256];
|
302
|
+
|
303
|
+
sprintf(text, "%s@%s (OPEN)", STR2CSTR(user), STR2CSTR(file));
|
304
|
+
result = rb_str_new2(text);
|
305
|
+
}
|
306
|
+
|
307
|
+
return(result);
|
308
|
+
}
|
309
|
+
|
310
|
+
|
311
|
+
/**
|
312
|
+
* This function provides the execute method for the Connection class.
|
313
|
+
*
|
314
|
+
* @param self A reference to the connection object to perform the
|
315
|
+
* execution through.
|
316
|
+
* @param sql A reference to the SQL statement to be executed.
|
317
|
+
* @param transaction A reference to the transction that the statement will
|
318
|
+
* be executed under.
|
319
|
+
*
|
320
|
+
* @return Either a ResultSet object for a query statement or nil for a
|
321
|
+
* non-query statement.
|
322
|
+
*
|
323
|
+
*/
|
324
|
+
static VALUE executeOnConnection(VALUE self, VALUE sql, VALUE transaction)
|
325
|
+
{
|
326
|
+
VALUE results = Qnil,
|
327
|
+
statement = rb_statement_new(self, transaction, sql, INT2FIX(3));
|
328
|
+
|
329
|
+
results = rb_execute_statement(statement);
|
330
|
+
if(results != Qnil && rb_obj_is_kind_of(results, rb_cInteger) == Qfalse)
|
331
|
+
{
|
332
|
+
if(rb_block_given_p())
|
333
|
+
{
|
334
|
+
VALUE row = rb_funcall(results, rb_intern("fetch"), 0),
|
335
|
+
last = Qnil;
|
336
|
+
|
337
|
+
while(row != Qnil)
|
338
|
+
{
|
339
|
+
last = rb_yield(row);
|
340
|
+
row = rb_funcall(results, rb_intern("fetch"), 0);
|
341
|
+
}
|
342
|
+
rb_funcall(results, rb_intern("close"), 0);
|
343
|
+
results = last;
|
344
|
+
}
|
345
|
+
}
|
346
|
+
rb_statement_close(statement);
|
347
|
+
|
348
|
+
return(results);
|
349
|
+
}
|
350
|
+
|
351
|
+
|
352
|
+
/**
|
353
|
+
* This function provides the execute_immediate method for the Connection class.
|
354
|
+
*
|
355
|
+
* @param self A reference to the connection object to perform the execution
|
356
|
+
* through.
|
357
|
+
* @param sql A reference to the SQL statement to be executed.
|
358
|
+
*
|
359
|
+
* @return Always returns nil.
|
360
|
+
*
|
361
|
+
*/
|
362
|
+
static VALUE executeOnConnectionImmediate(VALUE self, VALUE sql)
|
363
|
+
{
|
364
|
+
VALUE transaction = rb_transaction_new(self),
|
365
|
+
set = Qnil,
|
366
|
+
results = Qnil,
|
367
|
+
array = rb_ary_new(),
|
368
|
+
dialect = INT2FIX(3),
|
369
|
+
statement = rb_statement_new(self, transaction, sql, dialect);
|
370
|
+
|
371
|
+
rb_ary_push(array, self);
|
372
|
+
rb_ary_push(array, transaction);
|
373
|
+
rb_ary_push(array, sql);
|
374
|
+
rb_ary_push(array, statement);
|
375
|
+
|
376
|
+
set = rb_rescue(executeBlock, array, executeRescue, array);
|
377
|
+
if(set != Qnil)
|
378
|
+
{
|
379
|
+
if(TYPE(set) == T_DATA &&
|
380
|
+
RDATA(set)->dfree == (RUBY_DATA_FUNC)resultSetFree)
|
381
|
+
{
|
382
|
+
rb_assign_transaction(set, transaction);
|
383
|
+
if(rb_block_given_p())
|
384
|
+
{
|
385
|
+
results = rb_rescue(executeImmediateBlock, set,
|
386
|
+
executeImmediateRescue, set);
|
387
|
+
}
|
388
|
+
else
|
389
|
+
{
|
390
|
+
results = set;
|
391
|
+
}
|
392
|
+
}
|
393
|
+
else
|
394
|
+
{
|
395
|
+
rb_funcall(transaction, rb_intern("commit"), 0);
|
396
|
+
results = set;
|
397
|
+
}
|
398
|
+
}
|
399
|
+
else
|
400
|
+
{
|
401
|
+
rb_funcall(transaction, rb_intern("commit"), 0);
|
402
|
+
}
|
403
|
+
|
404
|
+
return(results);
|
405
|
+
}
|
406
|
+
|
407
|
+
|
408
|
+
/**
|
409
|
+
* This function provides the user accessor method for the Connection object.
|
410
|
+
*
|
411
|
+
* @param self A reference to the Connection object to fetch theuser from.
|
412
|
+
*
|
413
|
+
* @return A reference to the user name used to establish the connection.
|
414
|
+
*
|
415
|
+
*/
|
416
|
+
static VALUE getConnectionUser(VALUE self)
|
417
|
+
{
|
418
|
+
return(rb_iv_get(self, "@user"));
|
419
|
+
}
|
420
|
+
|
421
|
+
|
422
|
+
/**
|
423
|
+
* This function provides the block handling capabilities for the
|
424
|
+
* start_transaction method.
|
425
|
+
*
|
426
|
+
* @param transaction The Transaction object that was created for the block.
|
427
|
+
*
|
428
|
+
* @return A reference to the return value provided by the block.
|
429
|
+
*
|
430
|
+
*/
|
431
|
+
VALUE startTransactionBlock(VALUE transaction)
|
432
|
+
{
|
433
|
+
VALUE result = rb_yield(transaction);
|
434
|
+
|
435
|
+
rb_funcall(transaction, rb_intern("commit"), 0);
|
436
|
+
|
437
|
+
return(result);
|
438
|
+
}
|
439
|
+
|
440
|
+
|
441
|
+
/**
|
442
|
+
* This function provides the rescue handling capabilities for the
|
443
|
+
* start_transaction block handling functionality.
|
444
|
+
*
|
445
|
+
* @param transaction A reference to the Transaction object that was created
|
446
|
+
* @param error A reference to details relating to the exception raised.
|
447
|
+
* for the block.
|
448
|
+
*
|
449
|
+
* @return Would be nil but always throws an exception.
|
450
|
+
*
|
451
|
+
*/
|
452
|
+
VALUE startTransactionRescue(VALUE transaction, VALUE error)
|
453
|
+
{
|
454
|
+
rb_funcall(transaction, rb_intern("rollback"), 0);
|
455
|
+
rb_exc_raise(error);
|
456
|
+
return(Qnil);
|
457
|
+
}
|
458
|
+
|
459
|
+
|
460
|
+
/**
|
461
|
+
* This function is used to wrap the call to the executeOnConnection() function
|
462
|
+
* made by the executeOnConnectionImmediate() function to help insure that the
|
463
|
+
* transaction is rolled back in case of an error.
|
464
|
+
*
|
465
|
+
* @param array An array of the parameters for the function to use.
|
466
|
+
*
|
467
|
+
* @return The ResultSet object generated by execution or nil if it wasn't a
|
468
|
+
* query.
|
469
|
+
*
|
470
|
+
*/
|
471
|
+
VALUE executeBlock(VALUE array)
|
472
|
+
{
|
473
|
+
VALUE result = Qnil,
|
474
|
+
connection = rb_ary_entry(array, 0),
|
475
|
+
transaction = rb_ary_entry(array, 1),
|
476
|
+
sql = rb_ary_entry(array, 2),
|
477
|
+
statement = rb_ary_entry(array, 3);
|
478
|
+
|
479
|
+
result = rb_execute_statement(statement);
|
480
|
+
rb_statement_close(statement);
|
481
|
+
|
482
|
+
return(result);
|
483
|
+
}
|
484
|
+
|
485
|
+
|
486
|
+
/**
|
487
|
+
* This function provides clean up for the execution of a block associated
|
488
|
+
* with the execute method.
|
489
|
+
*
|
490
|
+
* @param array An array of the parameters for the function to use.
|
491
|
+
* @param error A reference to details relating to the exception raised.
|
492
|
+
*
|
493
|
+
* @return Would always returns nil except that it always raises an exception.
|
494
|
+
*
|
495
|
+
*/
|
496
|
+
VALUE executeRescue(VALUE array, VALUE error)
|
497
|
+
{
|
498
|
+
VALUE transaction = rb_ary_entry(array, 1),
|
499
|
+
statement = rb_ary_entry(array, 3);
|
500
|
+
|
501
|
+
rb_funcall(transaction, rb_intern("rollback"), 0);
|
502
|
+
rb_statement_close(statement);
|
503
|
+
rb_exc_raise(error);
|
504
|
+
return(Qnil);
|
505
|
+
}
|
506
|
+
|
507
|
+
|
508
|
+
/**
|
509
|
+
* This function is executed to process a block passed to the execute_immedate
|
510
|
+
* method.
|
511
|
+
*
|
512
|
+
* @param set A reference to the ResultSet to be processed by the block.
|
513
|
+
*
|
514
|
+
* @return A reference to the return value generated by the block.
|
515
|
+
*
|
516
|
+
*/
|
517
|
+
VALUE executeImmediateBlock(VALUE set)
|
518
|
+
{
|
519
|
+
VALUE result = Qnil,
|
520
|
+
row = rb_funcall(set, rb_intern("fetch"), 0);
|
521
|
+
|
522
|
+
while(row != Qnil)
|
523
|
+
{
|
524
|
+
result = rb_yield(row);
|
525
|
+
row = rb_funcall(set, rb_intern("fetch"), 0);
|
526
|
+
}
|
527
|
+
rb_funcall(set, rb_intern("close"), 0);
|
528
|
+
|
529
|
+
return(result);
|
530
|
+
}
|
531
|
+
|
532
|
+
|
533
|
+
/**
|
534
|
+
* This function provides clean up for the execution of a block associated
|
535
|
+
* with the execute_immediate method.
|
536
|
+
*
|
537
|
+
* @param set A reference to the ResultSet object for the block.
|
538
|
+
* @param error A reference to details relating to the exception raised.
|
539
|
+
*
|
540
|
+
* @return Would always returns nil except that it always raises an exception.
|
541
|
+
*
|
542
|
+
*/
|
543
|
+
VALUE executeImmediateRescue(VALUE set, VALUE error)
|
544
|
+
{
|
545
|
+
rb_funcall(set, rb_intern("close"), 0);
|
546
|
+
rb_exc_raise(error);
|
547
|
+
return(Qnil);
|
548
|
+
}
|
549
|
+
|
550
|
+
|
551
|
+
/**
|
552
|
+
* This method creates a database parameter buffer to be used in creating a
|
553
|
+
* database connection.
|
554
|
+
*
|
555
|
+
* @param user A reference to a string containing the user name to be used
|
556
|
+
* in making the connection.
|
557
|
+
* @param password A reference to a string containing the password to be used
|
558
|
+
* in making the connection.
|
559
|
+
* @param options A hash of the options to be used in making the connection
|
560
|
+
* to the database.
|
561
|
+
* @param length A pointer to a short integer that will be set to the
|
562
|
+
* length of the buffer.
|
563
|
+
*
|
564
|
+
* @return A pointer to an array of characters containing the database
|
565
|
+
* parameter buffer.
|
566
|
+
*
|
567
|
+
*/
|
568
|
+
char *createDPB(VALUE user, VALUE password, VALUE options, short *length)
|
569
|
+
{
|
570
|
+
char *dpb = NULL;
|
571
|
+
VALUE keys;
|
572
|
+
VALUE entry;
|
573
|
+
int i;
|
574
|
+
short type;
|
575
|
+
|
576
|
+
/* Determine the dpb length and allocate it. */
|
577
|
+
*length = 1;
|
578
|
+
if(user != Qnil)
|
579
|
+
{
|
580
|
+
*length += strlen(StringValuePtr(user)) + 2;
|
581
|
+
}
|
582
|
+
if(password != Qnil)
|
583
|
+
{
|
584
|
+
*length += strlen(StringValuePtr(password)) + 2;
|
585
|
+
}
|
586
|
+
if(options != Qnil)
|
587
|
+
{
|
588
|
+
keys = rb_funcall(options, rb_intern("keys"), 0);
|
589
|
+
|
590
|
+
for(i = 0; i < RARRAY_LEN(keys); i++)
|
591
|
+
{
|
592
|
+
type = FIX2INT(rb_ary_entry(keys, i));
|
593
|
+
|
594
|
+
switch (type)
|
595
|
+
{
|
596
|
+
case isc_dpb_sql_role_name:
|
597
|
+
case isc_dpb_lc_messages:
|
598
|
+
case isc_dpb_lc_ctype:
|
599
|
+
case isc_dpb_reserved:
|
600
|
+
{
|
601
|
+
entry = rb_hash_aref(options, INT2FIX(type));
|
602
|
+
*length += strlen(StringValuePtr(entry)) + 2;
|
603
|
+
break;
|
604
|
+
}
|
605
|
+
default:
|
606
|
+
{
|
607
|
+
*length += 3;
|
608
|
+
}
|
609
|
+
}
|
610
|
+
}
|
611
|
+
}
|
612
|
+
dpb = ALLOC_N(char, *length);
|
613
|
+
|
614
|
+
/* Populate the buffer. */
|
615
|
+
if(dpb != NULL)
|
616
|
+
{
|
617
|
+
char *ptr = NULL;
|
618
|
+
int size = 0;
|
619
|
+
|
620
|
+
/* Fill out the DPB. */
|
621
|
+
memset(dpb, 0, *length);
|
622
|
+
dpb[0] = isc_dpb_version1;
|
623
|
+
ptr = &dpb[1];
|
624
|
+
|
625
|
+
if(user != Qnil)
|
626
|
+
{
|
627
|
+
char *username = StringValuePtr(user);
|
628
|
+
|
629
|
+
size = strlen(username);
|
630
|
+
*ptr++ = isc_dpb_user_name;
|
631
|
+
*ptr++ = (char)size;
|
632
|
+
memcpy(ptr, username, size);
|
633
|
+
ptr = ptr + size;
|
634
|
+
}
|
635
|
+
|
636
|
+
if(password != Qnil)
|
637
|
+
{
|
638
|
+
char *userpwd = StringValuePtr(password);
|
639
|
+
|
640
|
+
size = strlen(userpwd);
|
641
|
+
*ptr++ = isc_dpb_password;
|
642
|
+
*ptr++ = (char)size;
|
643
|
+
memcpy(ptr, userpwd, size);
|
644
|
+
ptr = ptr + size;
|
645
|
+
}
|
646
|
+
|
647
|
+
if(options != Qnil)
|
648
|
+
{
|
649
|
+
for(i = 0; i < RARRAY_LEN(keys); i++)
|
650
|
+
{
|
651
|
+
type = FIX2INT(rb_ary_entry(keys, i));
|
652
|
+
entry = rb_hash_aref(options, INT2FIX(type));
|
653
|
+
|
654
|
+
switch (type)
|
655
|
+
{
|
656
|
+
case isc_dpb_sql_role_name:
|
657
|
+
case isc_dpb_lc_messages:
|
658
|
+
case isc_dpb_lc_ctype:
|
659
|
+
case isc_dpb_reserved:
|
660
|
+
{
|
661
|
+
char *text = StringValuePtr(entry);
|
662
|
+
|
663
|
+
size = strlen(text);
|
664
|
+
*ptr++ = type;
|
665
|
+
*ptr++ = (char)size;
|
666
|
+
memcpy(ptr, text, size);
|
667
|
+
ptr = ptr + size;
|
668
|
+
break;
|
669
|
+
}
|
670
|
+
default:
|
671
|
+
{
|
672
|
+
short value;
|
673
|
+
switch (TYPE(entry))
|
674
|
+
{
|
675
|
+
case T_FIXNUM : value = FIX2INT(entry);
|
676
|
+
case T_NIL : value = 0;
|
677
|
+
case T_FALSE : value = 0;
|
678
|
+
case T_TRUE : value = 1;
|
679
|
+
case T_UNDEF : value = 0;
|
680
|
+
case T_FLOAT : value = NUM2INT(entry);
|
681
|
+
case T_BIGNUM : value = NUM2INT(entry);
|
682
|
+
default : value = 0;
|
683
|
+
}
|
684
|
+
|
685
|
+
*ptr++ = type;
|
686
|
+
*ptr++ = (char)1;
|
687
|
+
*ptr++ = value;
|
688
|
+
}
|
689
|
+
}
|
690
|
+
}
|
691
|
+
}
|
692
|
+
}
|
693
|
+
else
|
694
|
+
{
|
695
|
+
/* Generate an error. */
|
696
|
+
rb_raise(rb_eNoMemError,
|
697
|
+
"Memory allocation failure creating database DPB.");
|
698
|
+
}
|
699
|
+
|
700
|
+
return(dpb);
|
701
|
+
}
|
702
|
+
|
703
|
+
|
704
|
+
/**
|
705
|
+
* This function allows integration with the Ruby garbage collector to insure
|
706
|
+
* that the resources associated with a Connection object are released.
|
707
|
+
*
|
708
|
+
* @param connection A pointer to the ConnectionHandle structure associated
|
709
|
+
* with a Connection object.
|
710
|
+
*
|
711
|
+
*/
|
712
|
+
void connectionFree(void *connection)
|
713
|
+
{
|
714
|
+
if(connection != NULL)
|
715
|
+
{
|
716
|
+
ConnectionHandle *handle = (ConnectionHandle *)connection;
|
717
|
+
|
718
|
+
if(handle->handle != 0)
|
719
|
+
{
|
720
|
+
ISC_STATUS status[20];
|
721
|
+
|
722
|
+
isc_detach_database(status, &handle->handle);
|
723
|
+
}
|
724
|
+
free(handle);
|
725
|
+
}
|
726
|
+
}
|
727
|
+
|
728
|
+
|
729
|
+
/**
|
730
|
+
* This function provides a programatic way of creating a new Connection
|
731
|
+
* object.
|
732
|
+
*
|
733
|
+
* @param database A reference to the database that the connection will relate
|
734
|
+
* to.
|
735
|
+
* @param user A reference to the database user name to be used in making
|
736
|
+
* the connection.
|
737
|
+
* @param password A reference to the database password to be used in making
|
738
|
+
* the connection.
|
739
|
+
* @param options A hash of the options to be used in creating the connection
|
740
|
+
* object.
|
741
|
+
*
|
742
|
+
* @return A reference to the newly created Connection object.
|
743
|
+
*
|
744
|
+
*/
|
745
|
+
VALUE rb_connection_new(VALUE database, VALUE user, VALUE password, VALUE options)
|
746
|
+
{
|
747
|
+
VALUE connection = allocateConnection(cConnection),
|
748
|
+
parameters[4];
|
749
|
+
|
750
|
+
parameters[0] = database;
|
751
|
+
parameters[1] = user;
|
752
|
+
parameters[2] = password;
|
753
|
+
parameters[3] = options;
|
754
|
+
|
755
|
+
initializeConnection(4, parameters, connection);
|
756
|
+
|
757
|
+
return(connection);
|
758
|
+
}
|
759
|
+
|
760
|
+
|
761
|
+
/**
|
762
|
+
* This function is called to record the beginnings of a transactions against
|
763
|
+
* a related connection.
|
764
|
+
*
|
765
|
+
* @param transaction A reference to the newly created Transaction object.
|
766
|
+
* @param connection Either a reference to a Connection object or an Array
|
767
|
+
* of Connection objects that are included in the
|
768
|
+
* transaction.
|
769
|
+
*
|
770
|
+
*/
|
771
|
+
void rb_tx_started(VALUE transaction, VALUE connection)
|
772
|
+
{
|
773
|
+
VALUE array = TYPE(connection) == T_ARRAY ? connection : rb_ary_new(),
|
774
|
+
number = Qnil;
|
775
|
+
long size = 0,
|
776
|
+
index;
|
777
|
+
|
778
|
+
if(TYPE(connection) != T_ARRAY)
|
779
|
+
{
|
780
|
+
rb_ary_push(array, connection);
|
781
|
+
}
|
782
|
+
number = rb_funcall(array, rb_intern("size"), 0);
|
783
|
+
size = TYPE(number) == T_FIXNUM ? FIX2INT(number) : NUM2INT(number);
|
784
|
+
|
785
|
+
for(index = 0; index < size; index++)
|
786
|
+
{
|
787
|
+
VALUE entry = rb_ary_entry(array, index),
|
788
|
+
list = rb_iv_get(entry, "@transactions");
|
789
|
+
|
790
|
+
rb_ary_push(list, transaction);
|
791
|
+
}
|
792
|
+
}
|
793
|
+
|
794
|
+
|
795
|
+
/**
|
796
|
+
* This function is invoked by a Transaction object whenever it is committed or
|
797
|
+
* rolled back. The connection can then discount the Transaction from its list
|
798
|
+
* of transaction to be cleaned up and close time.
|
799
|
+
*
|
800
|
+
* @param connection A reference to the Connection object or an array of
|
801
|
+
* Connection objects that is to be informed about the
|
802
|
+
* transaction.
|
803
|
+
* @param transaction A reference to the Transaction object that is to be
|
804
|
+
* released.
|
805
|
+
*
|
806
|
+
*/
|
807
|
+
void rb_tx_released(VALUE connection, VALUE transaction)
|
808
|
+
{
|
809
|
+
VALUE array = TYPE(connection) == T_ARRAY ? connection : rb_ary_new(),
|
810
|
+
number = Qnil;
|
811
|
+
long size = 0,
|
812
|
+
index;
|
813
|
+
|
814
|
+
if(TYPE(connection) != T_ARRAY)
|
815
|
+
{
|
816
|
+
rb_ary_push(array, connection);
|
817
|
+
}
|
818
|
+
number = rb_funcall(array, rb_intern("size"), 0);
|
819
|
+
size = TYPE(number) == T_FIXNUM ? FIX2INT(number) : NUM2INT(number);
|
820
|
+
|
821
|
+
for(index = 0; index < size; index++)
|
822
|
+
{
|
823
|
+
VALUE entry = rb_ary_entry(array, index),
|
824
|
+
list = rb_iv_get(entry, "@transactions");
|
825
|
+
|
826
|
+
rb_ary_delete(list, transaction);
|
827
|
+
}
|
828
|
+
}
|
829
|
+
|
830
|
+
|
831
|
+
/**
|
832
|
+
* This function initializes the Connection class within the Ruby environment.
|
833
|
+
* The class is established under the module specified to the function.
|
834
|
+
*
|
835
|
+
* @param module A reference to the module to create the class within.
|
836
|
+
*
|
837
|
+
*/
|
838
|
+
void Init_Connection(VALUE module)
|
839
|
+
{
|
840
|
+
cConnection = rb_define_class_under(module, "Connection", rb_cObject);
|
841
|
+
rb_define_alloc_func(cConnection, allocateConnection);
|
842
|
+
rb_define_method(cConnection, "initialize", initializeConnection, -1);
|
843
|
+
rb_define_method(cConnection, "initialize_copy", forbidObjectCopy, 1);
|
844
|
+
rb_define_method(cConnection, "user", getConnectionUser, 0);
|
845
|
+
rb_define_method(cConnection, "open?", isConnectionOpen, 0);
|
846
|
+
rb_define_method(cConnection, "closed?", isConnectionClosed, 0);
|
847
|
+
rb_define_method(cConnection, "close", closeConnection, 0);
|
848
|
+
rb_define_method(cConnection, "database", getConnectionDatabase, 0);
|
849
|
+
rb_define_method(cConnection, "start_transaction", startConnectionTransaction, 0);
|
850
|
+
rb_define_method(cConnection, "to_s", connectionToString, 0);
|
851
|
+
rb_define_method(cConnection, "execute", executeOnConnection, 2);
|
852
|
+
rb_define_method(cConnection, "execute_immediate", executeOnConnectionImmediate, 1);
|
853
|
+
|
854
|
+
rb_define_const(cConnection, "MARK_DATABASE_DAMAGED", INT2FIX(isc_dpb_damaged));
|
855
|
+
rb_define_const(cConnection, "WRITE_POLICY", INT2FIX(isc_dpb_force_write));
|
856
|
+
rb_define_const(cConnection, "CHARACTER_SET", INT2FIX(isc_dpb_lc_ctype));
|
857
|
+
rb_define_const(cConnection, "MESSAGE_FILE", INT2FIX(isc_dpb_lc_messages));
|
858
|
+
rb_define_const(cConnection, "NUMBER_OF_CACHE_BUFFERS", INT2FIX(isc_dpb_num_buffers));
|
859
|
+
rb_define_const(cConnection, "DBA_USER_NAME", INT2FIX(isc_dpb_sys_user_name));
|
860
|
+
rb_define_const(cConnection, "SQL_ROLE_NAME", INT2FIX(isc_dpb_sql_role_name));
|
861
|
+
rb_define_const(cConnection, "WRITE_ASYNCHRONOUS", INT2FIX(0));
|
862
|
+
rb_define_const(cConnection, "WRITE_SYNCHRONOUS", INT2FIX(1));
|
863
|
+
}
|