activerecord 1.15.6 → 2.0.0
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 +2454 -34
- data/README +1 -1
- data/RUNNING_UNIT_TESTS +3 -34
- data/Rakefile +98 -77
- data/install.rb +1 -1
- data/lib/active_record.rb +13 -22
- data/lib/active_record/aggregations.rb +38 -49
- data/lib/active_record/associations.rb +452 -333
- data/lib/active_record/associations/association_collection.rb +66 -20
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +38 -18
- data/lib/active_record/associations/has_one_association.rb +30 -14
- data/lib/active_record/attribute_methods.rb +253 -0
- data/lib/active_record/base.rb +719 -494
- data/lib/active_record/calculations.rb +62 -63
- data/lib/active_record/callbacks.rb +57 -83
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
- data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
- data/lib/active_record/fixtures.rb +503 -113
- data/lib/active_record/locking/optimistic.rb +72 -34
- data/lib/active_record/migration.rb +80 -57
- data/lib/active_record/observer.rb +13 -10
- data/lib/active_record/query_cache.rb +16 -57
- data/lib/active_record/reflection.rb +35 -38
- data/lib/active_record/schema.rb +5 -5
- data/lib/active_record/schema_dumper.rb +35 -13
- data/lib/active_record/serialization.rb +98 -0
- data/lib/active_record/serializers/json_serializer.rb +71 -0
- data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
- data/lib/active_record/timestamp.rb +20 -21
- data/lib/active_record/transactions.rb +39 -43
- data/lib/active_record/validations.rb +256 -107
- data/lib/active_record/version.rb +3 -3
- data/lib/activerecord.rb +1 -0
- data/test/aaa_create_tables_test.rb +15 -2
- data/test/abstract_unit.rb +24 -17
- data/test/active_schema_test_mysql.rb +20 -8
- data/test/adapter_test.rb +23 -5
- data/test/adapter_test_sqlserver.rb +15 -1
- data/test/aggregations_test.rb +16 -1
- data/test/all.sh +2 -2
- data/test/associations/ar_joins_test.rb +0 -0
- data/test/associations/callbacks_test.rb +51 -30
- data/test/associations/cascaded_eager_loading_test.rb +1 -29
- data/test/associations/eager_singularization_test.rb +145 -0
- data/test/associations/eager_test.rb +42 -6
- data/test/associations/extension_test.rb +6 -1
- data/test/associations/inner_join_association_test.rb +88 -0
- data/test/associations/join_model_test.rb +47 -16
- data/test/associations_test.rb +449 -226
- data/test/attribute_methods_test.rb +97 -0
- data/test/base_test.rb +251 -105
- data/test/binary_test.rb +22 -27
- data/test/calculations_test.rb +37 -5
- data/test/callbacks_test.rb +23 -0
- data/test/connection_test_firebird.rb +2 -2
- data/test/connection_test_mysql.rb +30 -0
- data/test/connections/native_mysql/connection.rb +3 -0
- data/test/connections/native_sqlite/connection.rb +5 -14
- data/test/connections/native_sqlite3/connection.rb +5 -14
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
- data/test/datatype_test_postgresql.rb +178 -27
- data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
- data/test/defaults_test.rb +8 -1
- data/test/deprecated_finder_test.rb +7 -128
- data/test/finder_test.rb +192 -54
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author.rb +12 -5
- data/test/fixtures/binaries.yml +130 -435
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +8 -1
- data/test/fixtures/computer.rb +1 -0
- data/test/fixtures/contact.rb +16 -0
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +4 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
- data/test/fixtures/db_definitions/firebird.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase.sql +5 -0
- data/test/fixtures/db_definitions/openbase.sql +41 -25
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +5 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
- data/test/fixtures/db_definitions/postgresql.sql +87 -58
- data/test/fixtures/db_definitions/postgresql2.sql +1 -2
- data/test/fixtures/db_definitions/schema.rb +280 -0
- data/test/fixtures/db_definitions/schema2.rb +11 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +4 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
- data/test/fixtures/db_definitions/sybase.sql +4 -0
- data/test/fixtures/developer.rb +10 -0
- data/test/fixtures/example.log +1 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/item.rb +7 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/joke.rb +0 -3
- data/test/fixtures/matey.rb +4 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/minimalistic.rb +2 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixins.yml +2 -100
- data/test/fixtures/parrot.rb +13 -0
- data/test/fixtures/parrots.yml +27 -0
- data/test/fixtures/parrots_pirates.yml +7 -0
- data/test/fixtures/pirate.rb +5 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/post.rb +1 -0
- data/test/fixtures/project.rb +3 -2
- data/test/fixtures/reserved_words/distinct.yml +5 -0
- data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
- data/test/fixtures/reserved_words/group.yml +14 -0
- data/test/fixtures/reserved_words/select.yml +8 -0
- data/test/fixtures/reserved_words/values.yml +7 -0
- data/test/fixtures/ship.rb +3 -0
- data/test/fixtures/ships.yml +5 -0
- data/test/fixtures/tagging.rb +4 -0
- data/test/fixtures/taggings.yml +8 -1
- data/test/fixtures/topic.rb +13 -1
- data/test/fixtures/treasure.rb +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures_test.rb +205 -24
- data/test/inheritance_test.rb +7 -1
- data/test/json_serialization_test.rb +180 -0
- data/test/lifecycle_test.rb +1 -1
- data/test/locking_test.rb +85 -2
- data/test/migration_test.rb +206 -40
- data/test/mixin_test.rb +13 -515
- data/test/pk_test.rb +3 -6
- data/test/query_cache_test.rb +104 -0
- data/test/reflection_test.rb +16 -0
- data/test/reserved_word_test_mysql.rb +177 -0
- data/test/schema_dumper_test.rb +38 -3
- data/test/serialization_test.rb +47 -0
- data/test/transactions_test.rb +74 -23
- data/test/unconnected_test.rb +1 -1
- data/test/validations_test.rb +322 -32
- data/test/xml_serialization_test.rb +121 -44
- metadata +48 -41
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -85
- data/lib/active_record/acts/list.rb +0 -256
- data/lib/active_record/acts/nested_set.rb +0 -211
- data/lib/active_record/acts/tree.rb +0 -96
- data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
- data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
- data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
- data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
- data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
- data/lib/active_record/deprecated_associations.rb +0 -104
- data/lib/active_record/deprecated_finders.rb +0 -44
- data/lib/active_record/vendor/simple.rb +0 -693
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -58
- data/test/connections/native_sqlserver/connection.rb +0 -23
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
- data/test/deprecated_associations_test.rb +0 -396
- data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
- data/test/fixtures/db_definitions/mysql.sql +0 -234
- data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/mysql2.sql +0 -5
- data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
- data/test/fixtures/db_definitions/sqlserver.sql +0 -243
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
- data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
- data/test/fixtures/mixin.rb +0 -63
- data/test/mixin_nested_set_test.rb +0 -196
@@ -1,591 +0,0 @@
|
|
1
|
-
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
-
|
3
|
-
require 'bigdecimal'
|
4
|
-
require 'bigdecimal/util'
|
5
|
-
|
6
|
-
# sqlserver_adapter.rb -- ActiveRecord adapter for Microsoft SQL Server
|
7
|
-
#
|
8
|
-
# Author: Joey Gibson <joey@joeygibson.com>
|
9
|
-
# Date: 10/14/2004
|
10
|
-
#
|
11
|
-
# Modifications: DeLynn Berry <delynnb@megastarfinancial.com>
|
12
|
-
# Date: 3/22/2005
|
13
|
-
#
|
14
|
-
# Modifications (ODBC): Mark Imbriaco <mark.imbriaco@pobox.com>
|
15
|
-
# Date: 6/26/2005
|
16
|
-
|
17
|
-
# Modifications (Migrations): Tom Ward <tom@popdog.net>
|
18
|
-
# Date: 27/10/2005
|
19
|
-
#
|
20
|
-
# Modifications (Numerous fixes as maintainer): Ryan Tomayko <rtomayko@gmail.com>
|
21
|
-
# Date: Up to July 2006
|
22
|
-
|
23
|
-
# Current maintainer: Tom Ward <tom@popdog.net>
|
24
|
-
|
25
|
-
module ActiveRecord
|
26
|
-
class Base
|
27
|
-
def self.sqlserver_connection(config) #:nodoc:
|
28
|
-
require_library_or_gem 'dbi' unless self.class.const_defined?(:DBI)
|
29
|
-
|
30
|
-
config = config.symbolize_keys
|
31
|
-
|
32
|
-
mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
|
33
|
-
username = config[:username] ? config[:username].to_s : 'sa'
|
34
|
-
password = config[:password] ? config[:password].to_s : ''
|
35
|
-
autocommit = config.key?(:autocommit) ? config[:autocommit] : true
|
36
|
-
if mode == "ODBC"
|
37
|
-
raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
|
38
|
-
dsn = config[:dsn]
|
39
|
-
driver_url = "DBI:ODBC:#{dsn}"
|
40
|
-
else
|
41
|
-
raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database)
|
42
|
-
database = config[:database]
|
43
|
-
host = config[:host] ? config[:host].to_s : 'localhost'
|
44
|
-
driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};"
|
45
|
-
end
|
46
|
-
conn = DBI.connect(driver_url, username, password)
|
47
|
-
conn["AutoCommit"] = autocommit
|
48
|
-
ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
|
49
|
-
end
|
50
|
-
end # class Base
|
51
|
-
|
52
|
-
module ConnectionAdapters
|
53
|
-
class SQLServerColumn < Column# :nodoc:
|
54
|
-
attr_reader :identity, :is_special
|
55
|
-
|
56
|
-
def initialize(name, default, sql_type = nil, identity = false, null = true) # TODO: check ok to remove scale_value = 0
|
57
|
-
super(name, default, sql_type, null)
|
58
|
-
@identity = identity
|
59
|
-
@is_special = sql_type =~ /text|ntext|image/i
|
60
|
-
# TODO: check ok to remove @scale = scale_value
|
61
|
-
# SQL Server only supports limits on *char and float types
|
62
|
-
@limit = nil unless @type == :float or @type == :string
|
63
|
-
end
|
64
|
-
|
65
|
-
def simplified_type(field_type)
|
66
|
-
case field_type
|
67
|
-
when /money/i then :decimal
|
68
|
-
when /image/i then :binary
|
69
|
-
when /bit/i then :boolean
|
70
|
-
when /uniqueidentifier/i then :string
|
71
|
-
else super
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def type_cast(value)
|
76
|
-
return nil if value.nil?
|
77
|
-
case type
|
78
|
-
when :datetime then cast_to_datetime(value)
|
79
|
-
when :timestamp then cast_to_time(value)
|
80
|
-
when :time then cast_to_time(value)
|
81
|
-
when :date then cast_to_datetime(value)
|
82
|
-
when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
|
83
|
-
else super
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def cast_to_time(value)
|
88
|
-
return value if value.is_a?(Time)
|
89
|
-
time_array = ParseDate.parsedate(value)
|
90
|
-
Time.send(Base.default_timezone, *time_array) rescue nil
|
91
|
-
end
|
92
|
-
|
93
|
-
def cast_to_datetime(value)
|
94
|
-
return value.to_time if value.is_a?(DBI::Timestamp)
|
95
|
-
|
96
|
-
if value.is_a?(Time)
|
97
|
-
if value.year != 0 and value.month != 0 and value.day != 0
|
98
|
-
return value
|
99
|
-
else
|
100
|
-
return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
if value.is_a?(DateTime)
|
105
|
-
return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
|
106
|
-
end
|
107
|
-
|
108
|
-
return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
|
109
|
-
value
|
110
|
-
end
|
111
|
-
|
112
|
-
# TODO: Find less hack way to convert DateTime objects into Times
|
113
|
-
|
114
|
-
def self.string_to_time(value)
|
115
|
-
if value.is_a?(DateTime)
|
116
|
-
return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
|
117
|
-
else
|
118
|
-
super
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# These methods will only allow the adapter to insert binary data with a length of 7K or less
|
123
|
-
# because of a SQL Server statement length policy.
|
124
|
-
def self.string_to_binary(value)
|
125
|
-
value.gsub(/(\r|\n|\0|\x1a)/) do
|
126
|
-
case $1
|
127
|
-
when "\r" then "%00"
|
128
|
-
when "\n" then "%01"
|
129
|
-
when "\0" then "%02"
|
130
|
-
when "\x1a" then "%03"
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def self.binary_to_string(value)
|
136
|
-
value.gsub(/(%00|%01|%02|%03)/) do
|
137
|
-
case $1
|
138
|
-
when "%00" then "\r"
|
139
|
-
when "%01" then "\n"
|
140
|
-
when "%02\0" then "\0"
|
141
|
-
when "%03" then "\x1a"
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# In ADO mode, this adapter will ONLY work on Windows systems,
|
148
|
-
# since it relies on Win32OLE, which, to my knowledge, is only
|
149
|
-
# available on Windows.
|
150
|
-
#
|
151
|
-
# This mode also relies on the ADO support in the DBI module. If you are using the
|
152
|
-
# one-click installer of Ruby, then you already have DBI installed, but
|
153
|
-
# the ADO module is *NOT* installed. You will need to get the latest
|
154
|
-
# source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
|
155
|
-
# unzip it, and copy the file
|
156
|
-
# <tt>src/lib/dbd_ado/ADO.rb</tt>
|
157
|
-
# to
|
158
|
-
# <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt>
|
159
|
-
# (you will more than likely need to create the ADO directory).
|
160
|
-
# Once you've installed that file, you are ready to go.
|
161
|
-
#
|
162
|
-
# In ODBC mode, the adapter requires the ODBC support in the DBI module which requires
|
163
|
-
# the Ruby ODBC module. Ruby ODBC 0.996 was used in development and testing,
|
164
|
-
# and it is available at http://www.ch-werner.de/rubyodbc/
|
165
|
-
#
|
166
|
-
# Options:
|
167
|
-
#
|
168
|
-
# * <tt>:mode</tt> -- ADO or ODBC. Defaults to ADO.
|
169
|
-
# * <tt>:username</tt> -- Defaults to sa.
|
170
|
-
# * <tt>:password</tt> -- Defaults to empty string.
|
171
|
-
#
|
172
|
-
# ADO specific options:
|
173
|
-
#
|
174
|
-
# * <tt>:host</tt> -- Defaults to localhost.
|
175
|
-
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
176
|
-
#
|
177
|
-
# ODBC specific options:
|
178
|
-
#
|
179
|
-
# * <tt>:dsn</tt> -- Defaults to nothing.
|
180
|
-
#
|
181
|
-
# ADO code tested on Windows 2000 and higher systems,
|
182
|
-
# running ruby 1.8.2 (2004-07-29) [i386-mswin32], and SQL Server 2000 SP3.
|
183
|
-
#
|
184
|
-
# ODBC code tested on a Fedora Core 4 system, running FreeTDS 0.63,
|
185
|
-
# unixODBC 2.2.11, Ruby ODBC 0.996, Ruby DBI 0.0.23 and Ruby 1.8.2.
|
186
|
-
# [Linux strongmad 2.6.11-1.1369_FC4 #1 Thu Jun 2 22:55:56 EDT 2005 i686 i686 i386 GNU/Linux]
|
187
|
-
class SQLServerAdapter < AbstractAdapter
|
188
|
-
|
189
|
-
def initialize(connection, logger, connection_options=nil)
|
190
|
-
super(connection, logger)
|
191
|
-
@connection_options = connection_options
|
192
|
-
end
|
193
|
-
|
194
|
-
def native_database_types
|
195
|
-
{
|
196
|
-
:primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
|
197
|
-
:string => { :name => "varchar", :limit => 255 },
|
198
|
-
:text => { :name => "text" },
|
199
|
-
:integer => { :name => "int" },
|
200
|
-
:float => { :name => "float", :limit => 8 },
|
201
|
-
:decimal => { :name => "decimal" },
|
202
|
-
:datetime => { :name => "datetime" },
|
203
|
-
:timestamp => { :name => "datetime" },
|
204
|
-
:time => { :name => "datetime" },
|
205
|
-
:date => { :name => "datetime" },
|
206
|
-
:binary => { :name => "image"},
|
207
|
-
:boolean => { :name => "bit"}
|
208
|
-
}
|
209
|
-
end
|
210
|
-
|
211
|
-
def adapter_name
|
212
|
-
'SQLServer'
|
213
|
-
end
|
214
|
-
|
215
|
-
def supports_migrations? #:nodoc:
|
216
|
-
true
|
217
|
-
end
|
218
|
-
|
219
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
220
|
-
return super unless type.to_s == 'integer'
|
221
|
-
|
222
|
-
if limit.nil? || limit == 4
|
223
|
-
'integer'
|
224
|
-
elsif limit < 4
|
225
|
-
'smallint'
|
226
|
-
else
|
227
|
-
'bigint'
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
# CONNECTION MANAGEMENT ====================================#
|
232
|
-
|
233
|
-
# Returns true if the connection is active.
|
234
|
-
def active?
|
235
|
-
@connection.execute("SELECT 1").finish
|
236
|
-
true
|
237
|
-
rescue DBI::DatabaseError, DBI::InterfaceError
|
238
|
-
false
|
239
|
-
end
|
240
|
-
|
241
|
-
# Reconnects to the database, returns false if no connection could be made.
|
242
|
-
def reconnect!
|
243
|
-
disconnect!
|
244
|
-
@connection = DBI.connect(*@connection_options)
|
245
|
-
rescue DBI::DatabaseError => e
|
246
|
-
@logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger
|
247
|
-
false
|
248
|
-
end
|
249
|
-
|
250
|
-
# Disconnects from the database
|
251
|
-
|
252
|
-
def disconnect!
|
253
|
-
@connection.disconnect rescue nil
|
254
|
-
end
|
255
|
-
|
256
|
-
def columns(table_name, name = nil)
|
257
|
-
return [] if table_name.blank?
|
258
|
-
table_name = table_name.to_s if table_name.is_a?(Symbol)
|
259
|
-
table_name = table_name.split('.')[-1] unless table_name.nil?
|
260
|
-
table_name = table_name.gsub(/[\[\]]/, '')
|
261
|
-
sql = %Q{
|
262
|
-
SELECT
|
263
|
-
cols.COLUMN_NAME as ColName,
|
264
|
-
cols.COLUMN_DEFAULT as DefaultValue,
|
265
|
-
cols.NUMERIC_SCALE as numeric_scale,
|
266
|
-
cols.NUMERIC_PRECISION as numeric_precision,
|
267
|
-
cols.DATA_TYPE as ColType,
|
268
|
-
cols.IS_NULLABLE As IsNullable,
|
269
|
-
COL_LENGTH(cols.TABLE_NAME, cols.COLUMN_NAME) as Length,
|
270
|
-
COLUMNPROPERTY(OBJECT_ID(cols.TABLE_NAME), cols.COLUMN_NAME, 'IsIdentity') as IsIdentity,
|
271
|
-
cols.NUMERIC_SCALE as Scale
|
272
|
-
FROM INFORMATION_SCHEMA.COLUMNS cols
|
273
|
-
WHERE cols.TABLE_NAME = '#{table_name}'
|
274
|
-
}
|
275
|
-
# Comment out if you want to have the Columns select statment logged.
|
276
|
-
# Personally, I think it adds unnecessary bloat to the log.
|
277
|
-
# If you do comment it out, make sure to un-comment the "result" line that follows
|
278
|
-
result = log(sql, name) { @connection.select_all(sql) }
|
279
|
-
#result = @connection.select_all(sql)
|
280
|
-
columns = []
|
281
|
-
result.each do |field|
|
282
|
-
default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
|
283
|
-
if field[:ColType] =~ /numeric|decimal/i
|
284
|
-
type = "#{field[:ColType]}(#{field[:numeric_precision]},#{field[:numeric_scale]})"
|
285
|
-
else
|
286
|
-
type = "#{field[:ColType]}(#{field[:Length]})"
|
287
|
-
end
|
288
|
-
is_identity = field[:IsIdentity] == 1
|
289
|
-
is_nullable = field[:IsNullable] == 'YES'
|
290
|
-
columns << SQLServerColumn.new(field[:ColName], default, type, is_identity, is_nullable)
|
291
|
-
end
|
292
|
-
columns
|
293
|
-
end
|
294
|
-
|
295
|
-
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
296
|
-
execute(sql, name)
|
297
|
-
id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
|
298
|
-
end
|
299
|
-
|
300
|
-
def update(sql, name = nil)
|
301
|
-
execute(sql, name) do |handle|
|
302
|
-
handle.rows
|
303
|
-
end || select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
|
304
|
-
end
|
305
|
-
|
306
|
-
alias_method :delete, :update
|
307
|
-
|
308
|
-
def execute(sql, name = nil)
|
309
|
-
if sql =~ /^\s*INSERT/i && (table_name = query_requires_identity_insert?(sql))
|
310
|
-
log(sql, name) do
|
311
|
-
with_identity_insert_enabled(table_name) do
|
312
|
-
@connection.execute(sql) do |handle|
|
313
|
-
yield(handle) if block_given?
|
314
|
-
end
|
315
|
-
end
|
316
|
-
end
|
317
|
-
else
|
318
|
-
log(sql, name) do
|
319
|
-
@connection.execute(sql) do |handle|
|
320
|
-
yield(handle) if block_given?
|
321
|
-
end
|
322
|
-
end
|
323
|
-
end
|
324
|
-
end
|
325
|
-
|
326
|
-
def begin_db_transaction
|
327
|
-
@connection["AutoCommit"] = false
|
328
|
-
rescue Exception => e
|
329
|
-
@connection["AutoCommit"] = true
|
330
|
-
end
|
331
|
-
|
332
|
-
def commit_db_transaction
|
333
|
-
@connection.commit
|
334
|
-
ensure
|
335
|
-
@connection["AutoCommit"] = true
|
336
|
-
end
|
337
|
-
|
338
|
-
def rollback_db_transaction
|
339
|
-
@connection.rollback
|
340
|
-
ensure
|
341
|
-
@connection["AutoCommit"] = true
|
342
|
-
end
|
343
|
-
|
344
|
-
def quote(value, column = nil)
|
345
|
-
return value.quoted_id if value.respond_to?(:quoted_id)
|
346
|
-
|
347
|
-
case value
|
348
|
-
when TrueClass then '1'
|
349
|
-
when FalseClass then '0'
|
350
|
-
when Time, DateTime then "'#{value.strftime("%Y%m%d %H:%M:%S")}'"
|
351
|
-
when Date then "'#{value.strftime("%Y%m%d")}'"
|
352
|
-
else super
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
def quote_string(string)
|
357
|
-
string.gsub(/\'/, "''")
|
358
|
-
end
|
359
|
-
|
360
|
-
def quote_column_name(name)
|
361
|
-
"[#{name}]"
|
362
|
-
end
|
363
|
-
|
364
|
-
def add_limit_offset!(sql, options)
|
365
|
-
if options[:limit] and options[:offset]
|
366
|
-
total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally")[0][:TotalRows].to_i
|
367
|
-
if (options[:limit] + options[:offset]) >= total_rows
|
368
|
-
options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
|
369
|
-
end
|
370
|
-
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ")
|
371
|
-
sql << ") AS tmp1"
|
372
|
-
if options[:order]
|
373
|
-
options[:order] = options[:order].split(',').map do |field|
|
374
|
-
parts = field.split(" ")
|
375
|
-
tc = parts[0]
|
376
|
-
if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
|
377
|
-
tc.gsub!(/\./, '\\.\\[')
|
378
|
-
tc << '\\]'
|
379
|
-
end
|
380
|
-
if sql =~ /#{tc} AS (t\d_r\d\d?)/
|
381
|
-
parts[0] = $1
|
382
|
-
elsif parts[0] =~ /\w+\.(\w+)/
|
383
|
-
parts[0] = $1
|
384
|
-
end
|
385
|
-
parts.join(' ')
|
386
|
-
end.join(', ')
|
387
|
-
sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
|
388
|
-
else
|
389
|
-
sql << " ) AS tmp2"
|
390
|
-
end
|
391
|
-
elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
|
392
|
-
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
|
393
|
-
"SELECT#{$1} TOP #{options[:limit]}"
|
394
|
-
end unless options[:limit].nil?
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
def recreate_database(name)
|
399
|
-
drop_database(name)
|
400
|
-
create_database(name)
|
401
|
-
end
|
402
|
-
|
403
|
-
def drop_database(name)
|
404
|
-
execute "DROP DATABASE #{name}"
|
405
|
-
end
|
406
|
-
|
407
|
-
def create_database(name)
|
408
|
-
execute "CREATE DATABASE #{name}"
|
409
|
-
end
|
410
|
-
|
411
|
-
def current_database
|
412
|
-
@connection.select_one("select DB_NAME()")[0]
|
413
|
-
end
|
414
|
-
|
415
|
-
def tables(name = nil)
|
416
|
-
execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", name) do |sth|
|
417
|
-
sth.inject([]) do |tables, field|
|
418
|
-
table_name = field[0]
|
419
|
-
tables << table_name unless table_name == 'dtproperties'
|
420
|
-
tables
|
421
|
-
end
|
422
|
-
end
|
423
|
-
end
|
424
|
-
|
425
|
-
def indexes(table_name, name = nil)
|
426
|
-
ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = false
|
427
|
-
indexes = []
|
428
|
-
execute("EXEC sp_helpindex '#{table_name}'", name) do |sth|
|
429
|
-
sth.each do |index|
|
430
|
-
unique = index[1] =~ /unique/
|
431
|
-
primary = index[1] =~ /primary key/
|
432
|
-
if !primary
|
433
|
-
indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
|
434
|
-
end
|
435
|
-
end
|
436
|
-
end
|
437
|
-
indexes
|
438
|
-
ensure
|
439
|
-
ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = true
|
440
|
-
end
|
441
|
-
|
442
|
-
def rename_table(name, new_name)
|
443
|
-
execute "EXEC sp_rename '#{name}', '#{new_name}'"
|
444
|
-
end
|
445
|
-
|
446
|
-
# Adds a new column to the named table.
|
447
|
-
# See TableDefinition#column for details of the options you can use.
|
448
|
-
def add_column(table_name, column_name, type, options = {})
|
449
|
-
add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
450
|
-
add_column_options!(add_column_sql, options)
|
451
|
-
# TODO: Add support to mimic date columns, using constraints to mark them as such in the database
|
452
|
-
# add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
|
453
|
-
execute(add_column_sql)
|
454
|
-
end
|
455
|
-
|
456
|
-
def rename_column(table, column, new_column_name)
|
457
|
-
execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
|
458
|
-
end
|
459
|
-
|
460
|
-
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
461
|
-
sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"]
|
462
|
-
if options_include_default?(options)
|
463
|
-
remove_default_constraint(table_name, column_name)
|
464
|
-
sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default], options[:column])} FOR #{column_name}"
|
465
|
-
end
|
466
|
-
sql_commands.each {|c|
|
467
|
-
execute(c)
|
468
|
-
}
|
469
|
-
end
|
470
|
-
|
471
|
-
def remove_column(table_name, column_name)
|
472
|
-
remove_check_constraints(table_name, column_name)
|
473
|
-
remove_default_constraint(table_name, column_name)
|
474
|
-
execute "ALTER TABLE [#{table_name}] DROP COLUMN [#{column_name}]"
|
475
|
-
end
|
476
|
-
|
477
|
-
def remove_default_constraint(table_name, column_name)
|
478
|
-
constraints = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
|
479
|
-
|
480
|
-
constraints.each do |constraint|
|
481
|
-
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
|
482
|
-
end
|
483
|
-
end
|
484
|
-
|
485
|
-
def remove_check_constraints(table_name, column_name)
|
486
|
-
# TODO remove all constraints in single method
|
487
|
-
constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
|
488
|
-
constraints.each do |constraint|
|
489
|
-
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
|
490
|
-
end
|
491
|
-
end
|
492
|
-
|
493
|
-
def remove_index(table_name, options = {})
|
494
|
-
execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
|
495
|
-
end
|
496
|
-
|
497
|
-
private
|
498
|
-
def select(sql, name = nil)
|
499
|
-
repair_special_columns(sql)
|
500
|
-
|
501
|
-
result = []
|
502
|
-
execute(sql) do |handle|
|
503
|
-
handle.each do |row|
|
504
|
-
row_hash = {}
|
505
|
-
row.each_with_index do |value, i|
|
506
|
-
if value.is_a? DBI::Timestamp
|
507
|
-
value = DateTime.new(value.year, value.month, value.day, value.hour, value.minute, value.sec)
|
508
|
-
end
|
509
|
-
row_hash[handle.column_names[i]] = value
|
510
|
-
end
|
511
|
-
result << row_hash
|
512
|
-
end
|
513
|
-
end
|
514
|
-
result
|
515
|
-
end
|
516
|
-
|
517
|
-
# Turns IDENTITY_INSERT ON for table during execution of the block
|
518
|
-
# N.B. This sets the state of IDENTITY_INSERT to OFF after the
|
519
|
-
# block has been executed without regard to its previous state
|
520
|
-
|
521
|
-
def with_identity_insert_enabled(table_name, &block)
|
522
|
-
set_identity_insert(table_name, true)
|
523
|
-
yield
|
524
|
-
ensure
|
525
|
-
set_identity_insert(table_name, false)
|
526
|
-
end
|
527
|
-
|
528
|
-
def set_identity_insert(table_name, enable = true)
|
529
|
-
execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
530
|
-
rescue Exception => e
|
531
|
-
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
532
|
-
end
|
533
|
-
|
534
|
-
def get_table_name(sql)
|
535
|
-
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
536
|
-
$1
|
537
|
-
elsif sql =~ /from\s+([^\(\s]+)\s*/i
|
538
|
-
$1
|
539
|
-
else
|
540
|
-
nil
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
def identity_column(table_name)
|
545
|
-
@table_columns = {} unless @table_columns
|
546
|
-
@table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
|
547
|
-
@table_columns[table_name].each do |col|
|
548
|
-
return col.name if col.identity
|
549
|
-
end
|
550
|
-
|
551
|
-
return nil
|
552
|
-
end
|
553
|
-
|
554
|
-
def query_requires_identity_insert?(sql)
|
555
|
-
table_name = get_table_name(sql)
|
556
|
-
id_column = identity_column(table_name)
|
557
|
-
sql =~ /\[#{id_column}\]/ ? table_name : nil
|
558
|
-
end
|
559
|
-
|
560
|
-
def change_order_direction(order)
|
561
|
-
order.split(",").collect {|fragment|
|
562
|
-
case fragment
|
563
|
-
when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
|
564
|
-
when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
|
565
|
-
else String.new(fragment).split(',').join(' DESC,') + ' DESC'
|
566
|
-
end
|
567
|
-
}.join(",")
|
568
|
-
end
|
569
|
-
|
570
|
-
def get_special_columns(table_name)
|
571
|
-
special = []
|
572
|
-
@table_columns ||= {}
|
573
|
-
@table_columns[table_name] ||= columns(table_name)
|
574
|
-
@table_columns[table_name].each do |col|
|
575
|
-
special << col.name if col.is_special
|
576
|
-
end
|
577
|
-
special
|
578
|
-
end
|
579
|
-
|
580
|
-
def repair_special_columns(sql)
|
581
|
-
special_cols = get_special_columns(get_table_name(sql))
|
582
|
-
for col in special_cols.to_a
|
583
|
-
sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
|
584
|
-
sql.gsub!(/ORDER BY #{col.to_s}/i, '')
|
585
|
-
end
|
586
|
-
sql
|
587
|
-
end
|
588
|
-
|
589
|
-
end #class SQLServerAdapter < AbstractAdapter
|
590
|
-
end #module ConnectionAdapters
|
591
|
-
end #module ActiveRecord
|