activerecord 1.9.0 → 1.9.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +13 -0
- data/lib/active_record/associations.rb +2 -2
- data/lib/active_record/base.rb +12 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +133 -127
- data/rakefile +140 -71
- data/test/column_alias_test.rb +1 -1
- data/test/connections/native_sqlserver/connection.rb +3 -0
- data/test/finder_test.rb +6 -5
- data/test/fixtures/db_definitions/sqlserver.drop.sql +1 -2
- data/test/fixtures/db_definitions/sqlserver.sql +35 -48
- metadata +3 -5
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
data/CHANGELOG
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
*1.9.1* (27th March, 2005)
|
2
|
+
|
3
|
+
* Fixed that Active Record objects with float attribute could not be cloned #808
|
4
|
+
|
5
|
+
* Fixed that MissingSourceFile's wasn't properly detected in production mode #925 [Nicholas Seckar]
|
6
|
+
|
7
|
+
* Fixed that :counter_cache option would look for a line_items_count column for a LineItem object instead of lineitems_count
|
8
|
+
|
9
|
+
* Fixed that AR exists?() would explode on postgresql if the passed id did not match the PK type #900 [Scott Barron]
|
10
|
+
|
11
|
+
* Fixed the MS SQL adapter to work with the new limit/offset approach and with binary data (still suffering from 7KB limit, though) #901 [delynnb]
|
12
|
+
|
13
|
+
|
1
14
|
*1.9.0* (22th March, 2005)
|
2
15
|
|
3
16
|
* Added adapter independent limit clause as a two-element array with the first being the limit, the second being the offset #795 [Sam Stephenson]. Example:
|
@@ -376,12 +376,12 @@ module ActiveRecord
|
|
376
376
|
|
377
377
|
if options[:counter_cache]
|
378
378
|
module_eval(
|
379
|
-
"after_create '#{association_class_name}.increment_counter(\"#{
|
379
|
+
"after_create '#{association_class_name}.increment_counter(\"#{self.to_s.underscore.pluralize + "_count"}\", #{association_class_primary_key_name})" +
|
380
380
|
" unless #{association_name}.nil?'"
|
381
381
|
)
|
382
382
|
|
383
383
|
module_eval(
|
384
|
-
"before_destroy '#{association_class_name}.decrement_counter(\"#{
|
384
|
+
"before_destroy '#{association_class_name}.decrement_counter(\"#{self.to_s.underscore.pluralize + "_count"}\", #{association_class_primary_key_name})" +
|
385
385
|
" unless #{association_name}.nil?'"
|
386
386
|
)
|
387
387
|
end
|
data/lib/active_record/base.rb
CHANGED
@@ -91,6 +91,15 @@ module ActiveRecord #:nodoc:
|
|
91
91
|
# on the other hand, will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query, which will ensure that
|
92
92
|
# an attacker can't escape the query and fake the login (or worse).
|
93
93
|
#
|
94
|
+
# When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
|
95
|
+
# question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
|
96
|
+
# the question marks with symbols and supplying a hash with values for the matching symbol keys:
|
97
|
+
#
|
98
|
+
# Company.find_first([
|
99
|
+
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
|
100
|
+
# { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
|
101
|
+
# ])
|
102
|
+
#
|
94
103
|
# == Overwriting default accessors
|
95
104
|
#
|
96
105
|
# All column values are automatically available through basic accessors on the Active Record object, but some times you
|
@@ -288,6 +297,7 @@ module ActiveRecord #:nodoc:
|
|
288
297
|
# Person.find(1, :conditions => "associate_id = 5"
|
289
298
|
# Person.find(1, 2, 6, :conditions => "status = 'active'"
|
290
299
|
# Person.find([7, 17], :conditions => ["sanitize_me = ?", "bare'quote"]
|
300
|
+
# Person.find(25, :conditions => ["name = :name AND age = :age", { :name => "Mary", :age => 22 }]
|
291
301
|
#
|
292
302
|
# +RecordNotFound+ is raised if no record can be found.
|
293
303
|
def find(*args)
|
@@ -330,7 +340,7 @@ module ActiveRecord #:nodoc:
|
|
330
340
|
# Example:
|
331
341
|
# Person.exists?(5)
|
332
342
|
def exists?(id)
|
333
|
-
!find_first("#{primary_key} = #{sanitize(id)}").nil?
|
343
|
+
!find_first("#{primary_key} = #{sanitize(id)}").nil? rescue false
|
334
344
|
end
|
335
345
|
|
336
346
|
# This method is deprecated in favor of find with the :conditions option.
|
@@ -1038,7 +1048,7 @@ module ActiveRecord #:nodoc:
|
|
1038
1048
|
self.attribute_names.inject({}) do |attributes, name|
|
1039
1049
|
begin
|
1040
1050
|
attributes[name] = read_attribute(name).clone
|
1041
|
-
rescue TypeError
|
1051
|
+
rescue TypeError, NoMethodError
|
1042
1052
|
attributes[name] = read_attribute(name)
|
1043
1053
|
end
|
1044
1054
|
attributes
|
@@ -5,6 +5,27 @@ require 'active_record/connection_adapters/abstract_adapter'
|
|
5
5
|
# Author: Joey Gibson <joey@joeygibson.com>
|
6
6
|
# Date: 10/14/2004
|
7
7
|
#
|
8
|
+
# Modifications: DeLynn Berry <delynnb@megastarfinancial.com>
|
9
|
+
# Date: 3/22/2005
|
10
|
+
#
|
11
|
+
# This adapter will ONLY work on Windows systems, since it relies on Win32OLE, which,
|
12
|
+
# to my knowledge, is only available on Window.
|
13
|
+
#
|
14
|
+
# It relies on the ADO support in the DBI module. If you are using the
|
15
|
+
# one-click installer of Ruby, then you already have DBI installed, but
|
16
|
+
# the ADO module is *NOT* installed. You will need to get the latest
|
17
|
+
# source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
|
18
|
+
# unzip it, and copy the file <tt>src/lib/dbd_ado/ADO.rb</tt> to
|
19
|
+
# <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt> (you will need to create
|
20
|
+
# the ADO directory). Once you've installed that file, you are ready to go.
|
21
|
+
#
|
22
|
+
# Options:
|
23
|
+
#
|
24
|
+
# * <tt>:host</tt> -- Defaults to localhost
|
25
|
+
# * <tt>:username</tt> -- Defaults to sa
|
26
|
+
# * <tt>:password</tt> -- Defaults to nothing
|
27
|
+
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
28
|
+
#
|
8
29
|
# I have tested this code on a WindowsXP Pro SP1 system,
|
9
30
|
# ruby 1.8.2 (2004-07-29) [i386-mswin32], SQL Server 2000.
|
10
31
|
#
|
@@ -19,7 +40,7 @@ module ActiveRecord
|
|
19
40
|
username = config[:username] ? config[:username].to_s : 'sa'
|
20
41
|
password = config[:password].to_s
|
21
42
|
|
22
|
-
if config.has_key?
|
43
|
+
if config.has_key?(:database)
|
23
44
|
database = config[:database]
|
24
45
|
else
|
25
46
|
raise ArgumentError, "No database specified. Missing argument: database."
|
@@ -38,92 +59,110 @@ module ActiveRecord
|
|
38
59
|
|
39
60
|
def initialize(name, default, sql_type = nil, is_identity = false, scale_value = 0)
|
40
61
|
super(name, default, sql_type)
|
41
|
-
|
42
62
|
@scale = scale_value
|
43
63
|
@identity = is_identity
|
44
|
-
end
|
45
|
-
|
46
|
-
def binary_to_string(value)
|
47
|
-
value
|
48
|
-
end
|
49
|
-
|
50
|
-
def string_to_binary(value)
|
51
|
-
value
|
52
|
-
end
|
64
|
+
end
|
53
65
|
|
54
66
|
def simplified_type(field_type)
|
55
67
|
case field_type
|
56
|
-
when /int/i
|
57
|
-
|
58
|
-
when /
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
68
|
+
when /int|bigint|smallint|tinyint/i : :integer
|
69
|
+
when /float|double|decimal|money|numeric|real|smallmoney/i : @scale == 0 ? :integer : :float
|
70
|
+
when /datetime|smalldatetime/i : :datetime
|
71
|
+
when /timestamp/i : :timestamp
|
72
|
+
when /time/i : :time
|
73
|
+
when /text|ntext/i : :text
|
74
|
+
when /binary|image|varbinary/i : :binary
|
75
|
+
when /char|nchar|nvarchar|string|varchar/i : :string
|
76
|
+
when /bit/i : :boolean
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def type_cast(value)
|
81
|
+
return nil if value.nil? || value =~ /^\s*null\s*$/i
|
82
|
+
case type
|
83
|
+
when :string then value
|
84
|
+
when :integer then value == true || value == false ? value == true ? '1' : '0' : value.to_i
|
85
|
+
when :float then value.to_f
|
86
|
+
when :datetime then cast_to_date_or_time(value)
|
87
|
+
when :timestamp then cast_to_time(value)
|
88
|
+
when :time then cast_to_time(value)
|
89
|
+
else value
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def cast_to_date_or_time(value)
|
94
|
+
return value if value.is_a?(Date)
|
95
|
+
guess_date_or_time (value.is_a?(Time)) ? value : cast_to_time(value)
|
96
|
+
end
|
97
|
+
|
98
|
+
def cast_to_time(value)
|
99
|
+
return value if value.is_a?(Time)
|
100
|
+
time_array = ParseDate.parsedate value
|
101
|
+
time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
|
102
|
+
Time.send Base.default_timezone, *time_array
|
103
|
+
end
|
104
|
+
|
105
|
+
def guess_date_or_time(value)
|
106
|
+
(value.hour == 0 and value.min == 0 and value.sec == 0) ?
|
107
|
+
Date.new(value.year, value.month, value.day) : value
|
108
|
+
end
|
109
|
+
|
110
|
+
# These methods will only allow the adapter to insert binary data with a length of 7K or less
|
111
|
+
# because of a SQL Server statement length policy.
|
112
|
+
def string_to_binary(value)
|
113
|
+
value.gsub(/(\r|\n|\0|\x1a)/) do
|
114
|
+
case $1
|
115
|
+
when "\r"
|
116
|
+
"%00"
|
117
|
+
when "\n"
|
118
|
+
"%01"
|
119
|
+
when "\0"
|
120
|
+
"%02"
|
121
|
+
when "\x1a"
|
122
|
+
"%03"
|
123
|
+
end
|
81
124
|
end
|
82
125
|
end
|
83
126
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
when
|
90
|
-
|
91
|
-
when
|
92
|
-
|
93
|
-
when
|
94
|
-
|
95
|
-
when 3
|
96
|
-
time_array[i] = time_array[i].nil? ? "0" : time_array[i].to_s
|
97
|
-
when 4
|
98
|
-
time_array[i] = time_array[i].nil? ? "0" : time_array[i].to_s
|
99
|
-
when 5
|
100
|
-
time_array[i] = time_array[i].nil? ? "0" : time_array[i].to_s
|
127
|
+
def binary_to_string(value)
|
128
|
+
value.gsub(/(%00|%01|%02|%03)/) do
|
129
|
+
case $1
|
130
|
+
when "%00"
|
131
|
+
"\r"
|
132
|
+
when "%01"
|
133
|
+
"\n"
|
134
|
+
when "%02\0"
|
135
|
+
"\0"
|
136
|
+
when "%03"
|
137
|
+
"\x1a"
|
101
138
|
end
|
102
139
|
end
|
103
|
-
# treat 0000-00-00 00:00:00 as nil
|
104
|
-
Time.send(Base.default_timezone, *time_array) rescue nil
|
105
140
|
end
|
106
141
|
|
107
142
|
end
|
108
143
|
|
109
|
-
# This adapter will ONLY work on Windows systems, since it relies on Win32OLE, which,
|
110
|
-
# to my knowledge, is only available on Window.
|
111
|
-
#
|
112
|
-
# It relies on the ADO support in the DBI module. If you are using the
|
113
|
-
# one-click installer of Ruby, then you already have DBI installed, but
|
114
|
-
# the ADO module is *NOT* installed. You will need to get the latest
|
115
|
-
# source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
|
116
|
-
# unzip it, and copy the file <tt>src/lib/dbd_ado/ADO.rb</tt> to
|
117
|
-
# <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt> (you will need to create
|
118
|
-
# the ADO directory). Once you've installed that file, you are ready to go.
|
119
|
-
#
|
120
|
-
# Options:
|
121
|
-
#
|
122
|
-
# * <tt>:host</tt> -- Defaults to localhost
|
123
|
-
# * <tt>:username</tt> -- Defaults to sa
|
124
|
-
# * <tt>:password</tt> -- Defaults to nothing
|
125
|
-
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
126
144
|
class SQLServerAdapter < AbstractAdapter
|
145
|
+
|
146
|
+
def native_database_types
|
147
|
+
{
|
148
|
+
:primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
|
149
|
+
:string => { :name => "varchar(255)" },
|
150
|
+
:text => { :name => "text(16)" },
|
151
|
+
:integer => { :name => "int(4)", :limit => 11 },
|
152
|
+
:float => { :name => "float(8)" },
|
153
|
+
:datetime => { :name => "datetime(8)" },
|
154
|
+
:timestamp => { :name => "datetime(8)" },
|
155
|
+
:time => { :name => "datetime(8)" },
|
156
|
+
:date => { :name => "datetime(8)" },
|
157
|
+
:binary => { :name => "image(16)" },
|
158
|
+
:boolean => { :name => "bit(1)" }
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
def adapter_name
|
163
|
+
'SQLServer'
|
164
|
+
end
|
165
|
+
|
127
166
|
def select_all(sql, name = nil)
|
128
167
|
add_limit!(sql, nil)
|
129
168
|
select(sql, name)
|
@@ -136,36 +175,22 @@ module ActiveRecord
|
|
136
175
|
end
|
137
176
|
|
138
177
|
def columns(table_name, name = nil)
|
139
|
-
sql =
|
140
|
-
SELECT
|
141
|
-
COLUMN_NAME as ColName,
|
142
|
-
COLUMN_DEFAULT as DefaultValue,
|
143
|
-
DATA_TYPE as ColType,
|
144
|
-
COL_LENGTH('#{table_name}', COLUMN_NAME) as Length,
|
145
|
-
COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity,
|
146
|
-
NUMERIC_SCALE as Scale
|
147
|
-
FROM INFORMATION_SCHEMA.columns
|
148
|
-
WHERE TABLE_NAME = '#{table_name}'
|
149
|
-
EOL
|
150
|
-
|
178
|
+
sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = '#{table_name}'"
|
151
179
|
result = nil
|
152
180
|
# Uncomment if you want to have the Columns select statment logged.
|
153
|
-
# Personnally, I think it adds unneccessary
|
181
|
+
# Personnally, I think it adds unneccessary bloat to the log.
|
154
182
|
# If you do uncomment, make sure to comment the "result" line that follows
|
155
183
|
log(sql, name, @connection) { |conn| result = conn.select_all(sql) }
|
156
184
|
#result = @connection.select_all(sql)
|
157
185
|
columns = []
|
158
186
|
result.each { |field| columns << ColumnWithIdentity.new(field[:ColName], field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue], "#{field[:ColType]}(#{field[:Length]})", field[:IsIdentity] == 1 ? true : false, field[:Scale]) }
|
159
|
-
|
160
187
|
columns
|
161
188
|
end
|
162
189
|
|
163
190
|
def insert(sql, name = nil, pk = nil, id_value = nil)
|
164
191
|
begin
|
165
192
|
table_name = get_table_name(sql)
|
166
|
-
|
167
193
|
col = get_identity_column(table_name)
|
168
|
-
|
169
194
|
ii_enabled = false
|
170
195
|
|
171
196
|
if col != nil
|
@@ -181,14 +206,12 @@ EOL
|
|
181
206
|
|
182
207
|
log(sql, name, @connection) do |conn|
|
183
208
|
conn.execute(sql)
|
184
|
-
|
185
209
|
select_one("SELECT @@IDENTITY AS Ident")["Ident"]
|
186
210
|
end
|
187
211
|
ensure
|
188
212
|
if ii_enabled
|
189
213
|
begin
|
190
214
|
execute enable_identity_insert(table_name, false)
|
191
|
-
|
192
215
|
rescue Exception => e
|
193
216
|
# Couldn't turn off IDENTITY_INSERT
|
194
217
|
end
|
@@ -196,11 +219,24 @@ EOL
|
|
196
219
|
end
|
197
220
|
end
|
198
221
|
|
222
|
+
def execute(sql, name = nil)
|
223
|
+
if sql =~ /^INSERT/i
|
224
|
+
insert(sql, name)
|
225
|
+
elsif sql =~ /^UPDATE|DELETE/i
|
226
|
+
log(sql, name, @connection) do |conn|
|
227
|
+
conn.execute(sql)
|
228
|
+
retVal = select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
|
229
|
+
end
|
230
|
+
else
|
231
|
+
log(sql, name, @connection) do |conn|
|
232
|
+
conn.execute(sql)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
199
237
|
def update(sql, name = nil)
|
200
238
|
execute(sql, name)
|
201
|
-
affected_rows(name)
|
202
239
|
end
|
203
|
-
|
204
240
|
alias_method :delete, :update
|
205
241
|
|
206
242
|
def begin_db_transaction
|
@@ -236,8 +272,8 @@ EOL
|
|
236
272
|
"'#{quote_string(value)}'"
|
237
273
|
end
|
238
274
|
when NilClass then "NULL"
|
239
|
-
when TrueClass then
|
240
|
-
when FalseClass then
|
275
|
+
when TrueClass then '1'
|
276
|
+
when FalseClass then '0'
|
241
277
|
when Float, Fixnum, Bignum then value.to_s
|
242
278
|
when Date then "'#{value.to_s}'"
|
243
279
|
when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
@@ -254,11 +290,12 @@ EOL
|
|
254
290
|
end
|
255
291
|
|
256
292
|
def add_limit_with_offset!(sql, limit, offset)
|
257
|
-
|
293
|
+
order_by = sql.include?("ORDER BY") ? get_order_by(sql.sub(/.*ORDER\sBY./, "")) : nil
|
294
|
+
sql.gsub!(/SELECT/i, "SELECT * FROM ( SELECT TOP #{limit} * FROM ( SELECT TOP #{limit + offset}")<<" ) AS tmp1 ORDER BY #{order_by[1]} ) AS tmp2 ORDER BY #{order_by[0]}"
|
258
295
|
end
|
259
296
|
|
260
297
|
def add_limit_without_offset!(sql, limit)
|
261
|
-
|
298
|
+
limit.nil? ? sql : sql.gsub!(/SELECT/i, "SELECT TOP #{limit}")
|
262
299
|
end
|
263
300
|
|
264
301
|
def recreate_database(name)
|
@@ -274,36 +311,18 @@ EOL
|
|
274
311
|
execute "CREATE DATABASE #{name}"
|
275
312
|
end
|
276
313
|
|
277
|
-
def execute(sql, name = nil)
|
278
|
-
if sql =~ /^INSERT/i
|
279
|
-
insert(sql, name)
|
280
|
-
else
|
281
|
-
log(sql, name, @connection) do |conn|
|
282
|
-
conn.execute(sql)
|
283
|
-
end
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
def adapter_name()
|
288
|
-
'SqlServer'
|
289
|
-
end
|
290
|
-
|
291
314
|
private
|
292
315
|
def select(sql, name = nil)
|
293
316
|
rows = []
|
294
|
-
|
295
317
|
log(sql, name, @connection) do |conn|
|
296
318
|
conn.select_all(sql) do |row|
|
297
319
|
record = {}
|
298
|
-
|
299
320
|
row.column_names.each do |col|
|
300
321
|
record[col] = row[col]
|
301
322
|
end
|
302
|
-
|
303
323
|
rows << record
|
304
324
|
end
|
305
325
|
end
|
306
|
-
|
307
326
|
rows
|
308
327
|
end
|
309
328
|
|
@@ -346,10 +365,6 @@ EOL
|
|
346
365
|
return sql =~ /[\(\.\,]\s*#{col}/
|
347
366
|
end
|
348
367
|
|
349
|
-
def query_contains_text_column(sql, col)
|
350
|
-
|
351
|
-
end
|
352
|
-
|
353
368
|
def get_order_by(sql)
|
354
369
|
return sql, sql.gsub(/\s*DESC\s*/, "").gsub(/\s*ASC\s*/, " DESC")
|
355
370
|
end
|
@@ -358,15 +373,6 @@ EOL
|
|
358
373
|
limit = limit.gsub!(/.OFFSET./i, ",").split(',')
|
359
374
|
return limit[0].to_i, limit[0].to_i+limit[1].to_i
|
360
375
|
end
|
361
|
-
|
362
|
-
def affected_rows(name = nil)
|
363
|
-
sql = "SELECT @@ROWCOUNT AS AffectedRows"
|
364
|
-
log(sql, name, @connection) do |conn|
|
365
|
-
conn.select_all(sql) do |row|
|
366
|
-
return row[:AffectedRows].to_i
|
367
|
-
end
|
368
|
-
end
|
369
|
-
end
|
370
376
|
end
|
371
377
|
end
|
372
378
|
end
|
data/rakefile
CHANGED
@@ -8,9 +8,14 @@ require 'rake/contrib/rubyforgepublisher'
|
|
8
8
|
|
9
9
|
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
10
10
|
PKG_NAME = 'activerecord'
|
11
|
-
PKG_VERSION = '1.9.
|
11
|
+
PKG_VERSION = '1.9.1' + PKG_BUILD
|
12
12
|
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
13
13
|
|
14
|
+
RELEASE_NAME = "REL #{PKG_VERSION}"
|
15
|
+
|
16
|
+
RUBY_FORGE_PROJECT = "activerecord"
|
17
|
+
RUBY_FORGE_USER = "webster132"
|
18
|
+
|
14
19
|
PKG_FILES = FileList[
|
15
20
|
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "rakefile"
|
16
21
|
].exclude(/\bCVS\b|~$/)
|
@@ -33,47 +38,14 @@ Rake::TestTask.new("test_mysql_ruby") { |t|
|
|
33
38
|
t.verbose = true
|
34
39
|
}
|
35
40
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
t.libs << "test" << "test/connections/native_sqlite"
|
44
|
-
t.pattern = 'test/*_test.rb'
|
45
|
-
t.verbose = true
|
46
|
-
}
|
47
|
-
|
48
|
-
Rake::TestTask.new("test_sqlite3") { |t|
|
49
|
-
t.libs << "test" << "test/connections/native_sqlite3"
|
50
|
-
t.pattern = 'test/*_test.rb'
|
51
|
-
t.verbose = true
|
52
|
-
}
|
53
|
-
|
54
|
-
Rake::TestTask.new("test_sqlserver") { |t|
|
55
|
-
t.libs << "test" << "test/connections/native_sqlserver"
|
56
|
-
t.pattern = 'test/*_test.rb'
|
57
|
-
t.verbose = true
|
58
|
-
}
|
59
|
-
|
60
|
-
Rake::TestTask.new("test_db2") { |t|
|
61
|
-
t.libs << "test" << "test/connections/native_db2"
|
62
|
-
t.pattern = 'test/*_test.rb'
|
63
|
-
t.verbose = true
|
64
|
-
}
|
65
|
-
|
66
|
-
Rake::TestTask.new("test_oracle") { |t|
|
67
|
-
t.libs << "test" << "test/connections/native_oracle"
|
68
|
-
t.pattern = 'test/*_test.rb'
|
69
|
-
t.verbose = true
|
70
|
-
}
|
41
|
+
for adapter in %( postgresql sqlite sqlite3 sqlserver db2 oci )
|
42
|
+
Rake::TestTask.new("test_#{adapter}") { |t|
|
43
|
+
t.libs << "test" << "test/connections/native_#{adapter}"
|
44
|
+
t.pattern = 'test/*_test.rb'
|
45
|
+
t.verbose = true
|
46
|
+
}
|
47
|
+
end
|
71
48
|
|
72
|
-
Rake::TestTask.new("test_oci") { |t|
|
73
|
-
t.libs << "test" << "test/connections/native_oci"
|
74
|
-
t.pattern = 'test/*_test.rb'
|
75
|
-
t.verbose = true
|
76
|
-
}
|
77
49
|
|
78
50
|
# Generate the RDoc documentation
|
79
51
|
|
@@ -81,6 +53,7 @@ Rake::RDocTask.new { |rdoc|
|
|
81
53
|
rdoc.rdoc_dir = 'doc'
|
82
54
|
rdoc.title = "Active Record -- Object-relation mapping put on rails"
|
83
55
|
rdoc.options << '--line-numbers --inline-source --accessor cattr_accessor=object'
|
56
|
+
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
84
57
|
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
|
85
58
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
86
59
|
rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
|
@@ -88,20 +61,6 @@ Rake::RDocTask.new { |rdoc|
|
|
88
61
|
}
|
89
62
|
|
90
63
|
|
91
|
-
# Publish beta gem
|
92
|
-
desc "Publish the beta gem"
|
93
|
-
task :pgem => [:package] do
|
94
|
-
Rake::SshFilePublisher.new("davidhh@comox.textdrive.com", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
95
|
-
`ssh davidhh@comox.textdrive.com './gemupdate.sh'`
|
96
|
-
end
|
97
|
-
|
98
|
-
# Publish documentation
|
99
|
-
desc "Publish the API documentation"
|
100
|
-
task :pdoc => [:rdoc] do
|
101
|
-
Rake::SshDirPublisher.new("davidhh@comox.textdrive.com", "public_html/ar", "doc").upload
|
102
|
-
end
|
103
|
-
|
104
|
-
|
105
64
|
# Create compressed packages
|
106
65
|
|
107
66
|
dist_dirs = [ "lib", "test", "examples", "dev-utils" ]
|
@@ -117,7 +76,7 @@ spec = Gem::Specification.new do |s|
|
|
117
76
|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
118
77
|
end
|
119
78
|
|
120
|
-
s.add_dependency('activesupport', '= 1.0.
|
79
|
+
s.add_dependency('activesupport', '= 1.0.3' + PKG_BUILD)
|
121
80
|
|
122
81
|
s.files.delete "test/fixtures/fixture_database.sqlite"
|
123
82
|
s.files.delete "test/fixtures/fixture_database_2.sqlite"
|
@@ -143,20 +102,130 @@ Rake::GemPackageTask.new(spec) do |p|
|
|
143
102
|
end
|
144
103
|
|
145
104
|
|
146
|
-
|
147
|
-
lines = 0
|
148
|
-
codelines = 0
|
149
|
-
Dir.foreach("lib/active_record") { |file_name|
|
150
|
-
next unless file_name =~ /.*rb/
|
151
|
-
|
152
|
-
f = File.open("lib/active_record/" + file_name)
|
105
|
+
# Publishing ------------------------------------------------------
|
153
106
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
107
|
+
desc "Publish the beta gem"
|
108
|
+
task :pgem => [:package] do
|
109
|
+
Rake::SshFilePublisher.new("davidhh@comox.textdrive.com", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
110
|
+
`ssh davidhh@comox.textdrive.com './gemupdate.sh'`
|
111
|
+
end
|
112
|
+
|
113
|
+
desc "Publish the API documentation"
|
114
|
+
task :pdoc => [:rdoc] do
|
115
|
+
Rake::SshDirPublisher.new("davidhh@comox.textdrive.com", "public_html/ar", "doc").upload
|
162
116
|
end
|
117
|
+
|
118
|
+
desc "Publish the release files to RubyForge."
|
119
|
+
task :release => [:package] do
|
120
|
+
files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
|
121
|
+
|
122
|
+
if RUBY_FORGE_PROJECT then
|
123
|
+
require 'net/http'
|
124
|
+
require 'open-uri'
|
125
|
+
|
126
|
+
project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
|
127
|
+
project_data = open(project_uri) { |data| data.read }
|
128
|
+
group_id = project_data[/[?&]group_id=(\d+)/, 1]
|
129
|
+
raise "Couldn't get group id" unless group_id
|
130
|
+
|
131
|
+
# This echos password to shell which is a bit sucky
|
132
|
+
if ENV["RUBY_FORGE_PASSWORD"]
|
133
|
+
password = ENV["RUBY_FORGE_PASSWORD"]
|
134
|
+
else
|
135
|
+
print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
|
136
|
+
password = STDIN.gets.chomp
|
137
|
+
end
|
138
|
+
|
139
|
+
login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
140
|
+
data = [
|
141
|
+
"login=1",
|
142
|
+
"form_loginname=#{RUBY_FORGE_USER}",
|
143
|
+
"form_pw=#{password}"
|
144
|
+
].join("&")
|
145
|
+
http.post("/account/login.php", data)
|
146
|
+
end
|
147
|
+
|
148
|
+
cookie = login_response["set-cookie"]
|
149
|
+
raise "Login failed" unless cookie
|
150
|
+
headers = { "Cookie" => cookie }
|
151
|
+
|
152
|
+
release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
|
153
|
+
release_data = open(release_uri, headers) { |data| data.read }
|
154
|
+
package_id = release_data[/[?&]package_id=(\d+)/, 1]
|
155
|
+
raise "Couldn't get package id" unless package_id
|
156
|
+
|
157
|
+
first_file = true
|
158
|
+
release_id = ""
|
159
|
+
|
160
|
+
files.each do |filename|
|
161
|
+
basename = File.basename(filename)
|
162
|
+
file_ext = File.extname(filename)
|
163
|
+
file_data = File.open(filename, "rb") { |file| file.read }
|
164
|
+
|
165
|
+
puts "Releasing #{basename}..."
|
166
|
+
|
167
|
+
release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
168
|
+
release_date = Time.now.strftime("%Y-%m-%d %H:%M")
|
169
|
+
type_map = {
|
170
|
+
".zip" => "3000",
|
171
|
+
".tgz" => "3110",
|
172
|
+
".gz" => "3110",
|
173
|
+
".gem" => "1400"
|
174
|
+
}; type_map.default = "9999"
|
175
|
+
type = type_map[file_ext]
|
176
|
+
boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
|
177
|
+
|
178
|
+
query_hash = if first_file then
|
179
|
+
{
|
180
|
+
"group_id" => group_id,
|
181
|
+
"package_id" => package_id,
|
182
|
+
"release_name" => RELEASE_NAME,
|
183
|
+
"release_date" => release_date,
|
184
|
+
"type_id" => type,
|
185
|
+
"processor_id" => "8000", # Any
|
186
|
+
"release_notes" => "",
|
187
|
+
"release_changes" => "",
|
188
|
+
"preformatted" => "1",
|
189
|
+
"submit" => "1"
|
190
|
+
}
|
191
|
+
else
|
192
|
+
{
|
193
|
+
"group_id" => group_id,
|
194
|
+
"release_id" => release_id,
|
195
|
+
"package_id" => package_id,
|
196
|
+
"step2" => "1",
|
197
|
+
"type_id" => type,
|
198
|
+
"processor_id" => "8000", # Any
|
199
|
+
"submit" => "Add This File"
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
query = "?" + query_hash.map do |(name, value)|
|
204
|
+
[name, URI.encode(value)].join("=")
|
205
|
+
end.join("&")
|
206
|
+
|
207
|
+
data = [
|
208
|
+
"--" + boundary,
|
209
|
+
"Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
|
210
|
+
"Content-Type: application/octet-stream",
|
211
|
+
"Content-Transfer-Encoding: binary",
|
212
|
+
"", file_data, ""
|
213
|
+
].join("\x0D\x0A")
|
214
|
+
|
215
|
+
release_headers = headers.merge(
|
216
|
+
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
|
217
|
+
)
|
218
|
+
|
219
|
+
target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
|
220
|
+
http.post(target + query, data, release_headers)
|
221
|
+
end
|
222
|
+
|
223
|
+
if first_file then
|
224
|
+
release_id = release_response.body[/release_id=(\d+)/, 1]
|
225
|
+
raise("Couldn't get release id") unless release_id
|
226
|
+
end
|
227
|
+
|
228
|
+
first_file = false
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
data/test/column_alias_test.rb
CHANGED
@@ -11,7 +11,7 @@ class TestColumnAlias < Test::Unit::TestCase
|
|
11
11
|
assert_equal(records[0].keys[0], "pk")
|
12
12
|
end
|
13
13
|
else
|
14
|
-
records = topic.connection.select_all("SELECT id AS pk FROM topics
|
14
|
+
records = topic.connection.select_all("SELECT id AS pk FROM topics")
|
15
15
|
assert_equal(records[0].keys[0], "pk")
|
16
16
|
end
|
17
17
|
end
|
data/test/finder_test.rb
CHANGED
@@ -2,10 +2,11 @@ require 'abstract_unit'
|
|
2
2
|
require 'fixtures/company'
|
3
3
|
require 'fixtures/topic'
|
4
4
|
require 'fixtures/entrant'
|
5
|
+
require 'fixtures/developer'
|
5
6
|
|
6
7
|
class FinderTest < Test::Unit::TestCase
|
7
8
|
fixtures :companies, :topics, :entrants, :developers
|
8
|
-
|
9
|
+
|
9
10
|
def test_find
|
10
11
|
assert_equal(@topics["first"]["title"], Topic.find(1).title)
|
11
12
|
end
|
@@ -43,10 +44,10 @@ class FinderTest < Test::Unit::TestCase
|
|
43
44
|
def test_find_all_with_prepared_limit_and_offset
|
44
45
|
if ActiveRecord::ConnectionAdapters.const_defined? :OracleAdapter
|
45
46
|
if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::OracleAdapter)
|
46
|
-
assert_raises(ArgumentError) { Entrant.find_all nil, "id ASC", [
|
47
|
+
assert_raises(ArgumentError) { Entrant.find_all nil, "id ASC", [2, 1] }
|
47
48
|
end
|
48
49
|
else
|
49
|
-
entrants = Entrant.find_all nil, "id ASC", [
|
50
|
+
entrants = Entrant.find_all nil, "id ASC", [2, 1]
|
50
51
|
|
51
52
|
assert_equal(2, entrants.size)
|
52
53
|
assert_equal(@entrants["second"]["name"], entrants.first.name)
|
@@ -256,11 +257,11 @@ class FinderTest < Test::Unit::TestCase
|
|
256
257
|
assert_equal first_five_developers, Developer.find_all(nil, 'id ASC', [5])
|
257
258
|
assert_equal no_developers, Developer.find_all(nil, 'id ASC', [0])
|
258
259
|
end
|
259
|
-
|
260
|
+
|
260
261
|
def test_find_all_with_limit_and_offset
|
261
262
|
first_three_developers = Developer.find_all nil, 'id ASC', [3, 0]
|
262
263
|
second_three_developers = Developer.find_all nil, 'id ASC', [3, 3]
|
263
|
-
last_two_developers = Developer.find_all nil, 'id ASC', [
|
264
|
+
last_two_developers = Developer.find_all nil, 'id ASC', [2, 8]
|
264
265
|
|
265
266
|
assert_equal 3, first_three_developers.length
|
266
267
|
assert_equal 3, second_three_developers.length
|
@@ -1,88 +1,79 @@
|
|
1
1
|
CREATE TABLE accounts (
|
2
|
-
id int NOT NULL IDENTITY(1, 1),
|
2
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
3
3
|
firm_id int default NULL,
|
4
|
-
credit_limit int default NULL
|
5
|
-
PRIMARY KEY (id)
|
4
|
+
credit_limit int default NULL
|
6
5
|
)
|
7
6
|
|
8
7
|
CREATE TABLE companies (
|
9
|
-
id int NOT NULL IDENTITY(1, 1),
|
8
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
10
9
|
type varchar(50) default NULL,
|
11
10
|
ruby_type varchar(50) default NULL,
|
12
11
|
firm_id int default NULL,
|
13
12
|
name varchar(50) default NULL,
|
14
13
|
client_of int default NULL,
|
15
|
-
|
16
|
-
rating int default 1,
|
17
|
-
PRIMARY KEY (id)
|
14
|
+
rating int default 1
|
18
15
|
)
|
19
16
|
|
20
17
|
CREATE TABLE topics (
|
21
|
-
id int NOT NULL IDENTITY(1, 1),
|
18
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
22
19
|
title varchar(255) default NULL,
|
23
20
|
author_name varchar(255) default NULL,
|
24
21
|
author_email_address varchar(255) default NULL,
|
25
22
|
written_on datetime default NULL,
|
23
|
+
bonus_time datetime default NULL,
|
26
24
|
last_read datetime default NULL,
|
27
25
|
content text,
|
28
26
|
approved tinyint default 1,
|
29
27
|
replies_count int default 0,
|
30
28
|
parent_id int default NULL,
|
31
|
-
type varchar(50) default NULL
|
32
|
-
PRIMARY KEY (id)
|
29
|
+
type varchar(50) default NULL
|
33
30
|
)
|
34
31
|
|
35
32
|
CREATE TABLE developers (
|
36
|
-
id int NOT NULL IDENTITY(1, 1),
|
33
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
37
34
|
name varchar(100) default NULL,
|
38
|
-
|
35
|
+
salary int default 70000
|
39
36
|
);
|
40
37
|
|
41
38
|
CREATE TABLE projects (
|
42
|
-
id int NOT NULL IDENTITY(1, 1),
|
43
|
-
name varchar(100) default NULL
|
44
|
-
salary int default 70000,
|
45
|
-
PRIMARY KEY (id)
|
39
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
40
|
+
name varchar(100) default NULL
|
46
41
|
);
|
47
42
|
|
48
43
|
CREATE TABLE developers_projects (
|
49
44
|
developer_id int NOT NULL,
|
50
|
-
project_id int NOT NULL
|
45
|
+
project_id int NOT NULL,
|
46
|
+
joined_on datetime default NULL
|
51
47
|
);
|
52
48
|
|
53
49
|
CREATE TABLE customers (
|
54
|
-
id int NOT NULL IDENTITY(1, 1),
|
50
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
55
51
|
name varchar(100) default NULL,
|
56
52
|
balance int default 0,
|
57
53
|
address_street varchar(100) default NULL,
|
58
54
|
address_city varchar(100) default NULL,
|
59
55
|
address_country varchar(100) default NULL,
|
60
|
-
gps_location varchar(100) default NULL
|
61
|
-
PRIMARY KEY (id)
|
56
|
+
gps_location varchar(100) default NULL
|
62
57
|
);
|
63
58
|
|
64
59
|
CREATE TABLE movies (
|
65
|
-
movieid int NOT NULL IDENTITY(1, 1),
|
66
|
-
name varchar(100) default NULL
|
67
|
-
PRIMARY KEY (movieid)
|
60
|
+
movieid int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
61
|
+
name varchar(100) default NULL
|
68
62
|
);
|
69
63
|
|
70
64
|
CREATE TABLE subscribers (
|
71
|
-
nick varchar(100) NOT NULL,
|
72
|
-
name varchar(100) default NULL
|
73
|
-
PRIMARY KEY (nick)
|
65
|
+
nick varchar(100) NOT NULL PRIMARY KEY,
|
66
|
+
name varchar(100) default NULL
|
74
67
|
);
|
75
68
|
|
76
69
|
CREATE TABLE booleantests (
|
77
|
-
id int NOT NULL IDENTITY(1, 1),
|
78
|
-
value
|
79
|
-
PRIMARY KEY (id)
|
70
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
71
|
+
value bit default NULL
|
80
72
|
);
|
81
73
|
|
82
74
|
CREATE TABLE auto_id_tests (
|
83
|
-
auto_id int NOT NULL IDENTITY(1, 1),
|
84
|
-
value int default NULL
|
85
|
-
PRIMARY KEY (auto_id)
|
75
|
+
auto_id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
76
|
+
value int default NULL
|
86
77
|
);
|
87
78
|
|
88
79
|
CREATE TABLE entrants (
|
@@ -92,22 +83,20 @@ CREATE TABLE entrants (
|
|
92
83
|
);
|
93
84
|
|
94
85
|
CREATE TABLE colnametests (
|
95
|
-
id int NOT NULL IDENTITY(1, 1),
|
96
|
-
[references] int NOT NULL
|
97
|
-
PRIMARY KEY (id)
|
86
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
87
|
+
[references] int NOT NULL
|
98
88
|
);
|
99
89
|
|
100
90
|
CREATE TABLE mixins (
|
101
|
-
id int NOT NULL IDENTITY(1, 1),
|
102
|
-
parent_id int default NULL,
|
103
|
-
type varchar(40) default NULL,
|
91
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
92
|
+
parent_id int default NULL,
|
104
93
|
pos int default NULL,
|
105
|
-
lft int default NULL,
|
106
|
-
rgt int default NULL,
|
107
|
-
root_id int default NULL,
|
108
94
|
created_at datetime default NULL,
|
109
95
|
updated_at datetime default NULL,
|
110
|
-
|
96
|
+
lft int default NULL,
|
97
|
+
rgt int default NULL,
|
98
|
+
root_id int default NULL,
|
99
|
+
type varchar(40) default NULL
|
111
100
|
);
|
112
101
|
|
113
102
|
CREATE TABLE people (
|
@@ -118,14 +107,12 @@ CREATE TABLE people (
|
|
118
107
|
);
|
119
108
|
|
120
109
|
CREATE TABLE binaries (
|
121
|
-
id int NOT NULL IDENTITY(1, 1),
|
122
|
-
data image NULL
|
123
|
-
PRIMARY KEY (id)
|
110
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
111
|
+
data image NULL
|
124
112
|
);
|
125
113
|
|
126
114
|
CREATE TABLE computers (
|
127
|
-
id int NOT NULL IDENTITY(1, 1),
|
128
|
-
developer int NOT NULL
|
129
|
-
PRIMARY KEY (id)
|
115
|
+
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
|
116
|
+
developer int NOT NULL
|
130
117
|
);
|
131
118
|
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.8
|
|
3
3
|
specification_version: 1
|
4
4
|
name: activerecord
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.9.
|
7
|
-
date: 2005-03-
|
6
|
+
version: 1.9.1
|
7
|
+
date: 2005-03-27
|
8
8
|
summary: Implements the ActiveRecord pattern for ORM.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -142,8 +142,6 @@ files:
|
|
142
142
|
- test/fixtures/developers_projects.yml
|
143
143
|
- test/fixtures/entrant.rb
|
144
144
|
- test/fixtures/entrants.yml
|
145
|
-
- test/fixtures/fixture_database.sqlite
|
146
|
-
- test/fixtures/fixture_database_2.sqlite
|
147
145
|
- test/fixtures/migrations
|
148
146
|
- test/fixtures/mixin.rb
|
149
147
|
- test/fixtures/mixins.yml
|
@@ -227,5 +225,5 @@ dependencies:
|
|
227
225
|
-
|
228
226
|
- "="
|
229
227
|
- !ruby/object:Gem::Version
|
230
|
-
version: 1.0.
|
228
|
+
version: 1.0.3
|
231
229
|
version:
|
Binary file
|
Binary file
|