odbc-rails 1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/AUTHORS +16 -0
  2. data/COPYING +21 -0
  3. data/ChangeLog +89 -0
  4. data/LICENSE +5 -0
  5. data/NEWS +12 -0
  6. data/README +282 -0
  7. data/lib/active_record/connection_adapters/odbc_adapter.rb +1792 -0
  8. data/lib/active_record/vendor/odbcext_db2.rb +87 -0
  9. data/lib/active_record/vendor/odbcext_informix.rb +132 -0
  10. data/lib/active_record/vendor/odbcext_informix_col.rb +45 -0
  11. data/lib/active_record/vendor/odbcext_ingres.rb +156 -0
  12. data/lib/active_record/vendor/odbcext_microsoftsqlserver.rb +185 -0
  13. data/lib/active_record/vendor/odbcext_microsoftsqlserver_col.rb +40 -0
  14. data/lib/active_record/vendor/odbcext_mysql.rb +136 -0
  15. data/lib/active_record/vendor/odbcext_oracle.rb +220 -0
  16. data/lib/active_record/vendor/odbcext_postgresql.rb +179 -0
  17. data/lib/active_record/vendor/odbcext_progress.rb +139 -0
  18. data/lib/active_record/vendor/odbcext_progress89.rb +259 -0
  19. data/lib/active_record/vendor/odbcext_sybase.rb +212 -0
  20. data/lib/active_record/vendor/odbcext_sybase_col.rb +49 -0
  21. data/lib/active_record/vendor/odbcext_virtuoso.rb +146 -0
  22. data/lib/odbc_adapter.rb +28 -0
  23. data/support/lib/active_record/connection_adapters/abstract/schema_definitions.rb +259 -0
  24. data/support/odbc_rails.diff +707 -0
  25. data/support/pack_odbc.rb +119 -0
  26. data/support/rake/rails_plugin_package_task.rb +212 -0
  27. data/support/test/base_test.rb +1349 -0
  28. data/support/test/migration_test.rb +566 -0
  29. data/test/connections/native_odbc/connection.rb +95 -0
  30. data/test/fixtures/db_definitions/db2.drop.sql +30 -0
  31. data/test/fixtures/db_definitions/db2.sql +217 -0
  32. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  33. data/test/fixtures/db_definitions/db22.sql +5 -0
  34. data/test/fixtures/db_definitions/informix.drop.sql +30 -0
  35. data/test/fixtures/db_definitions/informix.sql +205 -0
  36. data/test/fixtures/db_definitions/informix2.drop.sql +2 -0
  37. data/test/fixtures/db_definitions/informix2.sql +5 -0
  38. data/test/fixtures/db_definitions/ingres.drop.sql +62 -0
  39. data/test/fixtures/db_definitions/ingres.sql +232 -0
  40. data/test/fixtures/db_definitions/ingres2.drop.sql +2 -0
  41. data/test/fixtures/db_definitions/ingres2.sql +5 -0
  42. data/test/fixtures/db_definitions/mysql.drop.sql +30 -0
  43. data/test/fixtures/db_definitions/mysql.sql +219 -0
  44. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  45. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  46. data/test/fixtures/db_definitions/oracle_odbc.drop.sql +64 -0
  47. data/test/fixtures/db_definitions/oracle_odbc.sql +257 -0
  48. data/test/fixtures/db_definitions/oracle_odbc2.drop.sql +2 -0
  49. data/test/fixtures/db_definitions/oracle_odbc2.sql +6 -0
  50. data/test/fixtures/db_definitions/progress.drop.sql +61 -0
  51. data/test/fixtures/db_definitions/progress.sql +234 -0
  52. data/test/fixtures/db_definitions/progress2.drop.sql +2 -0
  53. data/test/fixtures/db_definitions/progress2.sql +6 -0
  54. data/test/fixtures/db_definitions/sqlserver.drop.sql +30 -0
  55. data/test/fixtures/db_definitions/sqlserver.sql +203 -0
  56. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  57. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  58. data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
  59. data/test/fixtures/db_definitions/sybase.sql +204 -0
  60. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  61. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  62. data/test/fixtures/db_definitions/virtuoso.drop.sql +30 -0
  63. data/test/fixtures/db_definitions/virtuoso.sql +200 -0
  64. data/test/fixtures/db_definitions/virtuoso2.drop.sql +2 -0
  65. data/test/fixtures/db_definitions/virtuoso2.sql +5 -0
  66. metadata +149 -0
@@ -0,0 +1,49 @@
1
+ #
2
+ # $Id: odbcext_sybase_col.rb,v 1.1 2006/12/06 14:42:11 source Exp $
3
+ #
4
+ # OpenLink ODBC Adapter for Ruby on Rails
5
+ # Copyright (C) 2006 OpenLink Software
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject
13
+ # to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
22
+ # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23
+ # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #
26
+
27
+ module ODBCColumnExt
28
+
29
+ # Is the column a numeric autoincrementing column?
30
+ def auto_unique?
31
+ @autounique
32
+ end
33
+
34
+ private
35
+
36
+ def autoUnique?
37
+ @nativeType =~ /\bidentity\b/i
38
+ end
39
+
40
+ #private
41
+
42
+ def default_preprocess(nativeType, default)
43
+ return default if default.nil?
44
+ default.replace($2.strip) if default =~ /(DEFAULT +)(.*)/i
45
+ default.replace($1) if default =~ /^'(.*)'$/
46
+ default
47
+ end
48
+
49
+ end # module
@@ -0,0 +1,146 @@
1
+ #
2
+ # $Id: odbcext_virtuoso.rb,v 1.1 2006/12/06 14:42:11 source Exp $
3
+ #
4
+ # OpenLink ODBC Adapter for Ruby on Rails
5
+ # Copyright (C) 2006 OpenLink Software
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject
13
+ # to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
22
+ # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23
+ # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #
26
+
27
+ module ODBCExt
28
+
29
+ # ------------------------------------------------------------------------
30
+ # Mandatory methods
31
+ #
32
+
33
+ # #last_insert_id must be implemented for any database which returns
34
+ # false from #prefetch_primary_key?
35
+
36
+ def last_insert_id(table, sequence_name, stmt = nil)
37
+ @logger.unknown("ODBCAdapter#last_insert_id>") if @trace
38
+ # 1049 (SQL_LASTSERIAL) is an ODBC extension for SQLGetStmtOption
39
+ stmt.get_option(1049)
40
+ end
41
+
42
+
43
+ # ------------------------------------------------------------------------
44
+ # Optional methods
45
+ #
46
+ # These are supplied for a DBMS only if necessary.
47
+ # ODBCAdapter tests for optional methods using Object#respond_to?
48
+
49
+ # Pre action for ODBCAdapter#insert
50
+ # def pre_insert(sql, name, pk, id_value, sequence_name)
51
+ # end
52
+
53
+ # Post action for ODBCAdapter#insert
54
+ # def post_insert(sql, name, pk, id_value, sequence_name)
55
+ # end
56
+
57
+ def set_sequence(table_name, pk)
58
+ begin
59
+ stmt = @connection.run("select max(#{pk}) + 1 from #{table_name}")
60
+ next_pk_val = stmt.fetch
61
+ stmt.drop
62
+ flds = table_name.split('.')
63
+ @connection.do("sequence_set('#{flds[0]}.#{flds[1]}.#{table_name}.#{pk}', #{next_pk_val}, 0)")
64
+ return true
65
+ rescue Exception => e
66
+ @logger.unknown("exception=#{e}") if @trace
67
+ end
68
+ return false
69
+ end
70
+
71
+ # ------------------------------------------------------------------------
72
+ # Method redefinitions
73
+ #
74
+ # DBMS specific methods which override the default implementation
75
+ # provided by the ODBCAdapter core.
76
+
77
+ def quote_string(s)
78
+ s.gsub(/\\/, '\&\&').gsub(/'/, "''")
79
+ end
80
+
81
+ def quoted_date(value)
82
+ @logger.unknown("ODBCAdapter#quoted_date>") if @trace
83
+ @logger.unknown("args=[#{value}]") if @trace
84
+ case value
85
+ # Ruby Time class includes a date component and so must be
86
+ # mapped to a Virtuoso DateTime type, not a Virtuoso Time type.
87
+ when Time, DateTime
88
+ %Q!stringdate('#{value.strftime("%Y-%m-%d %H:%M:%S")}')!
89
+ when Date
90
+ %Q!d('#{value.strftime("%Y-%m-%d")}')!
91
+ end
92
+ end
93
+
94
+ def rename_table(name, new_name)
95
+ @logger.unknown("ODBCAdapter#rename_table>") if @trace
96
+ @logger.unknown("args=[#{name}|#{new_name}]") if @trace
97
+ execute "ALTER TABLE #{name} RENAME #{new_name}"
98
+ rescue Exception => e
99
+ @logger.unknown("exception=#{e}") if @trace
100
+ raise ActiveRecord::ActiveRecordError, e.message
101
+ end
102
+
103
+ def change_column(table_name, column_name, type, options = {})
104
+ @logger.unknown("ODBCAdapter#change_column>") if @trace
105
+ @logger.unknown("args=[#{table_name}|#{column_name}|#{type}]") if @trace
106
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} " +
107
+ "#{type_to_sql(type)}"
108
+
109
+ # Add any :null and :default options
110
+ add_column_options!(change_column_sql, options)
111
+ execute(change_column_sql)
112
+ rescue Exception => e
113
+ @logger.unknown("exception=#{e}") if @trace
114
+ raise ActiveRecord::ActiveRecordError, e.message
115
+ end
116
+
117
+ def change_column_default(table_name, column_name, default)
118
+ @logger.unknown("ODBCAdapter#change_column_default>") if @trace
119
+ @logger.unknown("args=[#{table_name}|#{column_name}]") if @trace
120
+ change_column(table_name, column_name, nil, {:default => default})
121
+ rescue Exception => e
122
+ @logger.unknown("exception=#{e}") if @trace
123
+ raise ActiveRecord::ActiveRecordError, e.message
124
+ end
125
+
126
+ def remove_index(table_name, options = {})
127
+ @logger.unknown("ODBCAdapter#remove_index>") if @trace
128
+ @logger.unknown("args=[#{table_name}]") if @trace
129
+ execute "DROP INDEX #{quote_column_name(index_name(table_name, options))} #{table_name}"
130
+ rescue Exception => e
131
+ @logger.unknown("exception=#{e}") if @trace
132
+ raise ActiveRecord::ActiveRecordError, e.message
133
+ end
134
+
135
+ def indexes(table_name, name = nil)
136
+ # Virtuoso creates a unique index for a table's primary key.
137
+ # Hide any such index. The index name matches the table name.
138
+ #
139
+ # If this isn't done...
140
+ # Rails' 'rake test_units' attempts to create this index explicitly,
141
+ # but Virtuoso rejects this as the index has already been created
142
+ # automatically when the table was defined.
143
+ super(table_name, name).delete_if { |i| i.unique && i.name == table_name }
144
+ end
145
+
146
+ end
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # $Id: odbc_adapter.rb,v 1.1 2006/12/06 16:16:08 source Exp $
4
+ #
5
+ # OpenLink ODBC Adapter for Ruby on Rails
6
+ # Copyright (C) 2006 OpenLink Software
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining
9
+ # a copy of this software and associated documentation files (the
10
+ # "Software"), to deal in the Software without restriction, including
11
+ # without limitation the rights to use, copy, modify, merge, publish,
12
+ # distribute, sublicense, and/or sell copies of the Software, and to
13
+ # permit persons to whom the Software is furnished to do so, subject
14
+ # to the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be
17
+ # included in all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
23
+ # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
24
+ # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #
27
+
28
+ require 'active_record/connection_adapters/odbc_adapter'
@@ -0,0 +1,259 @@
1
+ require 'parsedate'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters #:nodoc:
5
+ # An abstract definition of a column in a table.
6
+ class Column
7
+ attr_reader :name, :default, :type, :limit, :null, :sql_type
8
+ attr_accessor :primary
9
+
10
+ # Instantiates a new column in the table.
11
+ #
12
+ # +name+ is the column's name, as in <tt><b>supplier_id</b> int(11)</tt>.
13
+ # +default+ is the type-casted default value, such as <tt>sales_stage varchar(20) default <b>'new'</b></tt>.
14
+ # +sql_type+ is only used to extract the column's length, if necessary. For example, <tt>company_name varchar(<b>60</b>)</tt>.
15
+ # +null+ determines if this column allows +NULL+ values.
16
+ def initialize(name, default, sql_type = nil, null = true)
17
+ @name, @type, @null = name, simplified_type(sql_type), null
18
+ @sql_type = sql_type
19
+ # have to do this one separately because type_cast depends on #type
20
+ @default = type_cast(default)
21
+ @limit = extract_limit(sql_type) unless sql_type.nil?
22
+ @primary = nil
23
+ @text = [:string, :text].include? @type
24
+ @number = [:float, :integer].include? @type
25
+ end
26
+
27
+ def text?
28
+ @text
29
+ end
30
+
31
+ def number?
32
+ @number
33
+ end
34
+
35
+ # Returns the Ruby class that corresponds to the abstract data type.
36
+ def klass
37
+ case type
38
+ when :integer then Fixnum
39
+ when :float then Float
40
+ when :datetime then Time
41
+ when :date then Date
42
+ when :timestamp then Time
43
+ when :time then Time
44
+ when :text, :string then String
45
+ when :binary then String
46
+ when :boolean then Object
47
+ end
48
+ end
49
+
50
+ # Casts value (which is a String) to an appropriate instance.
51
+ def type_cast(value)
52
+ return nil if value.nil?
53
+ case type
54
+ when :string then value
55
+ when :text then value
56
+ when :integer then value.to_i rescue value ? 1 : 0
57
+ when :float then value.to_f
58
+ when :datetime then self.class.string_to_time(value)
59
+ when :timestamp then self.class.string_to_time(value)
60
+ when :time then self.class.string_to_dummy_time(value)
61
+ when :date then self.class.string_to_date(value)
62
+ when :binary then self.class.binary_to_string(value)
63
+ when :boolean then self.class.value_to_boolean(value)
64
+ else value
65
+ end
66
+ end
67
+
68
+ def type_cast_code(var_name)
69
+ case type
70
+ when :string then nil
71
+ when :text then nil
72
+ when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
73
+ when :float then "#{var_name}.to_f"
74
+ when :datetime then "#{self.class.name}.string_to_time(#{var_name})"
75
+ when :timestamp then "#{self.class.name}.string_to_time(#{var_name})"
76
+ when :time then "#{self.class.name}.string_to_dummy_time(#{var_name})"
77
+ when :date then "#{self.class.name}.string_to_date(#{var_name})"
78
+ when :binary then "#{self.class.name}.binary_to_string(#{var_name})"
79
+ when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
80
+ else nil
81
+ end
82
+ end
83
+
84
+ # Returns the human name of the column name.
85
+ #
86
+ # ===== Examples
87
+ # Column.new('sales_stage', ...).human_name #=> 'Sales stage'
88
+ def human_name
89
+ Base.human_attribute_name(@name)
90
+ end
91
+
92
+ # Used to convert from Strings to BLOBs
93
+ def self.string_to_binary(value)
94
+ value
95
+ end
96
+
97
+ # Used to convert from BLOBs to Strings
98
+ def self.binary_to_string(value)
99
+ value
100
+ end
101
+
102
+ def self.string_to_date(string)
103
+ return string unless string.is_a?(String)
104
+ date_array = ParseDate.parsedate(string)
105
+ # treat 0000-00-00 as nil
106
+ Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
107
+ end
108
+
109
+ def self.string_to_time(string)
110
+ return string unless string.is_a?(String)
111
+ time_array = ParseDate.parsedate(string)[0..5]
112
+ # treat 0000-00-00 00:00:00 as nil
113
+ Time.send(Base.default_timezone, *time_array) rescue nil
114
+ end
115
+
116
+ def self.string_to_dummy_time(string)
117
+ return string unless string.is_a?(String)
118
+ time_array = ParseDate.parsedate(string)
119
+ # pad the resulting array with dummy date information
120
+ time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
121
+ Time.send(Base.default_timezone, *time_array) rescue nil
122
+ end
123
+
124
+ # convert something to a boolean
125
+ def self.value_to_boolean(value)
126
+ return value if value==true || value==false
127
+ case value.to_s.downcase
128
+ when "true", "t", "1" then true
129
+ else false
130
+ end
131
+ end
132
+
133
+ private
134
+ def extract_limit(sql_type)
135
+ $1.to_i if sql_type =~ /\((.*)\)/
136
+ end
137
+
138
+ def simplified_type(field_type)
139
+ case field_type
140
+ when /int/i
141
+ :integer
142
+ when /float|double|decimal|numeric/i
143
+ :float
144
+ when /datetime/i
145
+ :datetime
146
+ when /timestamp/i
147
+ :timestamp
148
+ when /time/i
149
+ :time
150
+ when /date/i
151
+ :date
152
+ when /clob/i, /text/i
153
+ :text
154
+ when /blob/i, /binary/i
155
+ :binary
156
+ when /char/i, /string/i
157
+ :string
158
+ when /boolean/i
159
+ :boolean
160
+ end
161
+ end
162
+ end
163
+
164
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
165
+ end
166
+
167
+ class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :default, :null) #:nodoc:
168
+ def to_sql
169
+ column_sql = "#{base.quote_column_name(name)} #{type_to_sql(type.to_sym, limit)}"
170
+ add_column_options!(column_sql, :null => null, :default => default)
171
+ column_sql
172
+ end
173
+ alias to_s :to_sql
174
+
175
+ private
176
+ def type_to_sql(name, limit)
177
+ base.type_to_sql(name, limit) rescue name
178
+ end
179
+
180
+ def add_column_options!(sql, options)
181
+ base.add_column_options!(sql, options.merge(:column => self))
182
+ end
183
+ end
184
+
185
+ # Represents a SQL table in an abstract way.
186
+ # Columns are stored as ColumnDefinition in the #columns attribute.
187
+ class TableDefinition
188
+ attr_accessor :columns
189
+
190
+ def initialize(base)
191
+ @columns = []
192
+ @base = base
193
+ end
194
+
195
+ # Appends a primary key definition to the table definition.
196
+ # Can be called multiple times, but this is probably not a good idea.
197
+ def primary_key(name)
198
+ column(name, :primary_key)
199
+ end
200
+
201
+ # Returns a ColumnDefinition for the column with name +name+.
202
+ def [](name)
203
+ @columns.find {|column| column.name.to_s == name.to_s}
204
+ end
205
+
206
+ # Instantiates a new column for the table.
207
+ # The +type+ parameter must be one of the following values:
208
+ # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
209
+ # <tt>:integer</tt>, <tt>:float</tt>, <tt>:datetime</tt>,
210
+ # <tt>:timestamp</tt>, <tt>:time</tt>, <tt>:date</tt>,
211
+ # <tt>:binary</tt>, <tt>:boolean</tt>.
212
+ #
213
+ # Available options are (none of these exists by default):
214
+ # * <tt>:limit</tt>:
215
+ # Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
216
+ # <tt>:binary</tt> or <tt>:integer</tt> columns only)
217
+ # * <tt>:default</tt>:
218
+ # The column's default value. You cannot explicitely set the default
219
+ # value to +NULL+. Simply leave off this option if you want a +NULL+
220
+ # default value.
221
+ # * <tt>:null</tt>:
222
+ # Allows or disallows +NULL+ values in the column. This option could
223
+ # have been named <tt>:null_allowed</tt>.
224
+ #
225
+ # This method returns <tt>self</tt>.
226
+ #
227
+ # ===== Examples
228
+ # # Assuming def is an instance of TableDefinition
229
+ # def.column(:granted, :boolean)
230
+ # #=> granted BOOLEAN
231
+ #
232
+ # def.column(:picture, :binary, :limit => 2.megabytes)
233
+ # #=> picture BLOB(2097152)
234
+ #
235
+ # def.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
236
+ # #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
237
+ def column(name, type, options = {})
238
+ column = self[name] || ColumnDefinition.new(@base, name, type)
239
+ column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym]
240
+ column.default = options[:default]
241
+ column.null = options[:null]
242
+ @columns << column unless @columns.include? column
243
+ self
244
+ end
245
+
246
+ # Returns a String whose contents are the column definitions
247
+ # concatenated together. This string can then be pre and appended to
248
+ # to generate the final SQL to create the table.
249
+ def to_sql
250
+ @columns * ', '
251
+ end
252
+
253
+ private
254
+ def native
255
+ @base.native_database_types
256
+ end
257
+ end
258
+ end
259
+ end