activerecord_authorails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. data/CHANGELOG +3043 -0
  2. data/README +360 -0
  3. data/RUNNING_UNIT_TESTS +64 -0
  4. data/Rakefile +226 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/associations.rb +87 -0
  7. data/examples/shared_setup.rb +15 -0
  8. data/examples/validation.rb +85 -0
  9. data/install.rb +30 -0
  10. data/lib/active_record.rb +85 -0
  11. data/lib/active_record/acts/list.rb +244 -0
  12. data/lib/active_record/acts/nested_set.rb +211 -0
  13. data/lib/active_record/acts/tree.rb +89 -0
  14. data/lib/active_record/aggregations.rb +191 -0
  15. data/lib/active_record/associations.rb +1637 -0
  16. data/lib/active_record/associations/association_collection.rb +190 -0
  17. data/lib/active_record/associations/association_proxy.rb +158 -0
  18. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  19. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  20. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +169 -0
  21. data/lib/active_record/associations/has_many_association.rb +210 -0
  22. data/lib/active_record/associations/has_many_through_association.rb +247 -0
  23. data/lib/active_record/associations/has_one_association.rb +80 -0
  24. data/lib/active_record/attribute_methods.rb +75 -0
  25. data/lib/active_record/base.rb +2164 -0
  26. data/lib/active_record/calculations.rb +270 -0
  27. data/lib/active_record/callbacks.rb +367 -0
  28. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +279 -0
  29. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +58 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +343 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +161 -0
  34. data/lib/active_record/connection_adapters/db2_adapter.rb +228 -0
  35. data/lib/active_record/connection_adapters/firebird_adapter.rb +728 -0
  36. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  37. data/lib/active_record/connection_adapters/mysql_adapter.rb +414 -0
  38. data/lib/active_record/connection_adapters/openbase_adapter.rb +350 -0
  39. data/lib/active_record/connection_adapters/oracle_adapter.rb +689 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +584 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +407 -0
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +591 -0
  43. data/lib/active_record/connection_adapters/sybase_adapter.rb +662 -0
  44. data/lib/active_record/deprecated_associations.rb +104 -0
  45. data/lib/active_record/deprecated_finders.rb +44 -0
  46. data/lib/active_record/fixtures.rb +628 -0
  47. data/lib/active_record/locking/optimistic.rb +106 -0
  48. data/lib/active_record/locking/pessimistic.rb +77 -0
  49. data/lib/active_record/migration.rb +394 -0
  50. data/lib/active_record/observer.rb +178 -0
  51. data/lib/active_record/query_cache.rb +64 -0
  52. data/lib/active_record/reflection.rb +222 -0
  53. data/lib/active_record/schema.rb +58 -0
  54. data/lib/active_record/schema_dumper.rb +149 -0
  55. data/lib/active_record/timestamp.rb +51 -0
  56. data/lib/active_record/transactions.rb +136 -0
  57. data/lib/active_record/validations.rb +843 -0
  58. data/lib/active_record/vendor/db2.rb +362 -0
  59. data/lib/active_record/vendor/mysql.rb +1214 -0
  60. data/lib/active_record/vendor/simple.rb +693 -0
  61. data/lib/active_record/version.rb +9 -0
  62. data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
  63. data/lib/active_record/wrappings.rb +58 -0
  64. data/lib/active_record/xml_serialization.rb +308 -0
  65. data/test/aaa_create_tables_test.rb +59 -0
  66. data/test/abstract_unit.rb +77 -0
  67. data/test/active_schema_test_mysql.rb +31 -0
  68. data/test/adapter_test.rb +87 -0
  69. data/test/adapter_test_sqlserver.rb +81 -0
  70. data/test/aggregations_test.rb +95 -0
  71. data/test/all.sh +8 -0
  72. data/test/ar_schema_test.rb +33 -0
  73. data/test/association_inheritance_reload.rb +14 -0
  74. data/test/associations/callbacks_test.rb +126 -0
  75. data/test/associations/cascaded_eager_loading_test.rb +138 -0
  76. data/test/associations/eager_test.rb +393 -0
  77. data/test/associations/extension_test.rb +42 -0
  78. data/test/associations/join_model_test.rb +497 -0
  79. data/test/associations_test.rb +1809 -0
  80. data/test/attribute_methods_test.rb +49 -0
  81. data/test/base_test.rb +1586 -0
  82. data/test/binary_test.rb +37 -0
  83. data/test/calculations_test.rb +219 -0
  84. data/test/callbacks_test.rb +377 -0
  85. data/test/class_inheritable_attributes_test.rb +32 -0
  86. data/test/column_alias_test.rb +17 -0
  87. data/test/connection_test_firebird.rb +8 -0
  88. data/test/connections/native_db2/connection.rb +25 -0
  89. data/test/connections/native_firebird/connection.rb +26 -0
  90. data/test/connections/native_frontbase/connection.rb +27 -0
  91. data/test/connections/native_mysql/connection.rb +24 -0
  92. data/test/connections/native_openbase/connection.rb +21 -0
  93. data/test/connections/native_oracle/connection.rb +27 -0
  94. data/test/connections/native_postgresql/connection.rb +23 -0
  95. data/test/connections/native_sqlite/connection.rb +34 -0
  96. data/test/connections/native_sqlite3/connection.rb +34 -0
  97. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  98. data/test/connections/native_sqlserver/connection.rb +23 -0
  99. data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
  100. data/test/connections/native_sybase/connection.rb +23 -0
  101. data/test/copy_table_sqlite.rb +64 -0
  102. data/test/datatype_test_postgresql.rb +52 -0
  103. data/test/default_test_firebird.rb +16 -0
  104. data/test/defaults_test.rb +60 -0
  105. data/test/deprecated_associations_test.rb +396 -0
  106. data/test/deprecated_finder_test.rb +151 -0
  107. data/test/empty_date_time_test.rb +25 -0
  108. data/test/finder_test.rb +504 -0
  109. data/test/fixtures/accounts.yml +28 -0
  110. data/test/fixtures/author.rb +99 -0
  111. data/test/fixtures/author_favorites.yml +4 -0
  112. data/test/fixtures/authors.yml +7 -0
  113. data/test/fixtures/auto_id.rb +4 -0
  114. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  115. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  116. data/test/fixtures/bad_fixtures/blank_line +3 -0
  117. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  118. data/test/fixtures/bad_fixtures/missing_value +1 -0
  119. data/test/fixtures/binary.rb +2 -0
  120. data/test/fixtures/categories.yml +14 -0
  121. data/test/fixtures/categories/special_categories.yml +9 -0
  122. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  123. data/test/fixtures/categories_ordered.yml +7 -0
  124. data/test/fixtures/categories_posts.yml +23 -0
  125. data/test/fixtures/categorization.rb +5 -0
  126. data/test/fixtures/categorizations.yml +17 -0
  127. data/test/fixtures/category.rb +20 -0
  128. data/test/fixtures/column_name.rb +3 -0
  129. data/test/fixtures/comment.rb +23 -0
  130. data/test/fixtures/comments.yml +59 -0
  131. data/test/fixtures/companies.yml +55 -0
  132. data/test/fixtures/company.rb +107 -0
  133. data/test/fixtures/company_in_module.rb +59 -0
  134. data/test/fixtures/computer.rb +3 -0
  135. data/test/fixtures/computers.yml +4 -0
  136. data/test/fixtures/course.rb +3 -0
  137. data/test/fixtures/courses.yml +7 -0
  138. data/test/fixtures/customer.rb +55 -0
  139. data/test/fixtures/customers.yml +17 -0
  140. data/test/fixtures/db_definitions/db2.drop.sql +32 -0
  141. data/test/fixtures/db_definitions/db2.sql +231 -0
  142. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  143. data/test/fixtures/db_definitions/db22.sql +5 -0
  144. data/test/fixtures/db_definitions/firebird.drop.sql +63 -0
  145. data/test/fixtures/db_definitions/firebird.sql +304 -0
  146. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  148. data/test/fixtures/db_definitions/frontbase.drop.sql +32 -0
  149. data/test/fixtures/db_definitions/frontbase.sql +268 -0
  150. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  151. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  152. data/test/fixtures/db_definitions/mysql.drop.sql +32 -0
  153. data/test/fixtures/db_definitions/mysql.sql +234 -0
  154. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  156. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  157. data/test/fixtures/db_definitions/openbase.sql +302 -0
  158. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  159. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  160. data/test/fixtures/db_definitions/oracle.drop.sql +65 -0
  161. data/test/fixtures/db_definitions/oracle.sql +325 -0
  162. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  163. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  164. data/test/fixtures/db_definitions/postgresql.drop.sql +37 -0
  165. data/test/fixtures/db_definitions/postgresql.sql +263 -0
  166. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  167. data/test/fixtures/db_definitions/postgresql2.sql +5 -0
  168. data/test/fixtures/db_definitions/schema.rb +60 -0
  169. data/test/fixtures/db_definitions/sqlite.drop.sql +32 -0
  170. data/test/fixtures/db_definitions/sqlite.sql +215 -0
  171. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  172. data/test/fixtures/db_definitions/sqlite2.sql +5 -0
  173. data/test/fixtures/db_definitions/sqlserver.drop.sql +34 -0
  174. data/test/fixtures/db_definitions/sqlserver.sql +243 -0
  175. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  176. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  177. data/test/fixtures/db_definitions/sybase.drop.sql +34 -0
  178. data/test/fixtures/db_definitions/sybase.sql +218 -0
  179. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  180. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  181. data/test/fixtures/default.rb +2 -0
  182. data/test/fixtures/developer.rb +52 -0
  183. data/test/fixtures/developers.yml +21 -0
  184. data/test/fixtures/developers_projects.yml +17 -0
  185. data/test/fixtures/developers_projects/david_action_controller +3 -0
  186. data/test/fixtures/developers_projects/david_active_record +3 -0
  187. data/test/fixtures/developers_projects/jamis_active_record +2 -0
  188. data/test/fixtures/edge.rb +5 -0
  189. data/test/fixtures/edges.yml +6 -0
  190. data/test/fixtures/entrant.rb +3 -0
  191. data/test/fixtures/entrants.yml +14 -0
  192. data/test/fixtures/fk_test_has_fk.yml +3 -0
  193. data/test/fixtures/fk_test_has_pk.yml +2 -0
  194. data/test/fixtures/flowers.jpg +0 -0
  195. data/test/fixtures/funny_jokes.yml +10 -0
  196. data/test/fixtures/joke.rb +6 -0
  197. data/test/fixtures/keyboard.rb +3 -0
  198. data/test/fixtures/legacy_thing.rb +3 -0
  199. data/test/fixtures/legacy_things.yml +3 -0
  200. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  201. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  202. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  203. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  204. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  205. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  206. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  207. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  208. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  209. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  210. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  211. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  212. data/test/fixtures/mixed_case_monkey.rb +3 -0
  213. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  214. data/test/fixtures/mixin.rb +63 -0
  215. data/test/fixtures/mixins.yml +127 -0
  216. data/test/fixtures/movie.rb +5 -0
  217. data/test/fixtures/movies.yml +7 -0
  218. data/test/fixtures/naked/csv/accounts.csv +1 -0
  219. data/test/fixtures/naked/yml/accounts.yml +1 -0
  220. data/test/fixtures/naked/yml/companies.yml +1 -0
  221. data/test/fixtures/naked/yml/courses.yml +1 -0
  222. data/test/fixtures/order.rb +4 -0
  223. data/test/fixtures/people.yml +3 -0
  224. data/test/fixtures/person.rb +4 -0
  225. data/test/fixtures/post.rb +58 -0
  226. data/test/fixtures/posts.yml +48 -0
  227. data/test/fixtures/project.rb +27 -0
  228. data/test/fixtures/projects.yml +7 -0
  229. data/test/fixtures/reader.rb +4 -0
  230. data/test/fixtures/readers.yml +4 -0
  231. data/test/fixtures/reply.rb +37 -0
  232. data/test/fixtures/subject.rb +4 -0
  233. data/test/fixtures/subscriber.rb +6 -0
  234. data/test/fixtures/subscribers/first +2 -0
  235. data/test/fixtures/subscribers/second +2 -0
  236. data/test/fixtures/tag.rb +7 -0
  237. data/test/fixtures/tagging.rb +6 -0
  238. data/test/fixtures/taggings.yml +18 -0
  239. data/test/fixtures/tags.yml +7 -0
  240. data/test/fixtures/task.rb +3 -0
  241. data/test/fixtures/tasks.yml +7 -0
  242. data/test/fixtures/topic.rb +25 -0
  243. data/test/fixtures/topics.yml +22 -0
  244. data/test/fixtures/vertex.rb +9 -0
  245. data/test/fixtures/vertices.yml +4 -0
  246. data/test/fixtures_test.rb +401 -0
  247. data/test/inheritance_test.rb +205 -0
  248. data/test/lifecycle_test.rb +137 -0
  249. data/test/locking_test.rb +190 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +768 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_nested_set_test.rb +196 -0
  254. data/test/mixin_test.rb +550 -0
  255. data/test/modules_test.rb +34 -0
  256. data/test/multiple_db_test.rb +60 -0
  257. data/test/pk_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +159 -0
  260. data/test/schema_authorization_test_postgresql.rb +75 -0
  261. data/test/schema_dumper_test.rb +96 -0
  262. data/test/schema_test_postgresql.rb +64 -0
  263. data/test/synonym_test_oracle.rb +17 -0
  264. data/test/table_name_test_sqlserver.rb +23 -0
  265. data/test/threaded_connections_test.rb +48 -0
  266. data/test/transactions_test.rb +230 -0
  267. data/test/unconnected_test.rb +32 -0
  268. data/test/validations_test.rb +1097 -0
  269. data/test/xml_serialization_test.rb +125 -0
  270. metadata +365 -0
@@ -0,0 +1,414 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'set'
3
+
4
+ module MysqlCompat #:nodoc:
5
+ # add all_hashes method to standard mysql-c bindings or pure ruby version
6
+ def self.define_all_hashes_method!
7
+ raise 'Mysql not loaded' unless defined?(::Mysql)
8
+
9
+ target = defined?(Mysql::Result) ? Mysql::Result : MysqlRes
10
+ return if target.instance_methods.include?('all_hashes')
11
+
12
+ # Ruby driver has a version string and returns null values in each_hash
13
+ # C driver >= 2.7 returns null values in each_hash
14
+ if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
15
+ target.class_eval <<-'end_eval'
16
+ def all_hashes
17
+ rows = []
18
+ each_hash { |row| rows << row }
19
+ rows
20
+ end
21
+ end_eval
22
+
23
+ # adapters before 2.7 don't have a version constant
24
+ # and don't return null values in each_hash
25
+ else
26
+ target.class_eval <<-'end_eval'
27
+ def all_hashes
28
+ rows = []
29
+ all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
30
+ each_hash { |row| rows << all_fields.dup.update(row) }
31
+ rows
32
+ end
33
+ end_eval
34
+ end
35
+
36
+ unless target.instance_methods.include?('all_hashes')
37
+ raise "Failed to defined #{target.name}#all_hashes method. Mysql::VERSION = #{Mysql::VERSION.inspect}"
38
+ end
39
+ end
40
+ end
41
+
42
+ module ActiveRecord
43
+ class Base
44
+ def self.require_mysql
45
+ # Include the MySQL driver if one hasn't already been loaded
46
+ unless defined? Mysql
47
+ begin
48
+ require_library_or_gem 'mysql'
49
+ rescue LoadError => cannot_require_mysql
50
+ # Use the bundled Ruby/MySQL driver if no driver is already in place
51
+ begin
52
+ require 'active_record/vendor/mysql'
53
+ rescue LoadError
54
+ raise cannot_require_mysql
55
+ end
56
+ end
57
+ end
58
+
59
+ # Define Mysql::Result.all_hashes
60
+ MysqlCompat.define_all_hashes_method!
61
+ end
62
+
63
+ # Establishes a connection to the database that's used by all Active Record objects.
64
+ def self.mysql_connection(config) # :nodoc:
65
+ config = config.symbolize_keys
66
+ host = config[:host]
67
+ port = config[:port]
68
+ socket = config[:socket]
69
+ username = config[:username] ? config[:username].to_s : 'root'
70
+ password = config[:password].to_s
71
+
72
+ if config.has_key?(:database)
73
+ database = config[:database]
74
+ else
75
+ raise ArgumentError, "No database specified. Missing argument: database."
76
+ end
77
+
78
+ require_mysql
79
+ mysql = Mysql.init
80
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
81
+
82
+ ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
83
+ end
84
+ end
85
+
86
+ module ConnectionAdapters
87
+ class MysqlColumn < Column #:nodoc:
88
+ TYPES_ALLOWING_EMPTY_STRING_DEFAULT = Set.new([:binary, :string, :text])
89
+
90
+ def initialize(name, default, sql_type = nil, null = true)
91
+ @original_default = default
92
+ super
93
+ @default = nil if missing_default_forged_as_empty_string?
94
+ end
95
+
96
+ private
97
+ def simplified_type(field_type)
98
+ return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
99
+ return :string if field_type =~ /enum/i
100
+ super
101
+ end
102
+
103
+ # MySQL misreports NOT NULL column default when none is given.
104
+ # We can't detect this for columns which may have a legitimate ''
105
+ # default (string, text, binary) but we can for others (integer,
106
+ # datetime, boolean, and the rest).
107
+ #
108
+ # Test whether the column has default '', is not null, and is not
109
+ # a type allowing default ''.
110
+ def missing_default_forged_as_empty_string?
111
+ !null && @original_default == '' && !TYPES_ALLOWING_EMPTY_STRING_DEFAULT.include?(type)
112
+ end
113
+ end
114
+
115
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
116
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
117
+ #
118
+ # Options:
119
+ #
120
+ # * <tt>:host</tt> -- Defaults to localhost
121
+ # * <tt>:port</tt> -- Defaults to 3306
122
+ # * <tt>:socket</tt> -- Defaults to /tmp/mysql.sock
123
+ # * <tt>:username</tt> -- Defaults to root
124
+ # * <tt>:password</tt> -- Defaults to nothing
125
+ # * <tt>:database</tt> -- The name of the database. No default, must be provided.
126
+ # * <tt>:sslkey</tt> -- Necessary to use MySQL with an SSL connection
127
+ # * <tt>:sslcert</tt> -- Necessary to use MySQL with an SSL connection
128
+ # * <tt>:sslcapath</tt> -- Necessary to use MySQL with an SSL connection
129
+ # * <tt>:sslcipher</tt> -- Necessary to use MySQL with an SSL connection
130
+ #
131
+ # By default, the MysqlAdapter will consider all columns of type tinyint(1)
132
+ # as boolean. If you wish to disable this emulation (which was the default
133
+ # behavior in versions 0.13.1 and earlier) you can add the following line
134
+ # to your environment.rb file:
135
+ #
136
+ # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
137
+ class MysqlAdapter < AbstractAdapter
138
+ @@emulate_booleans = true
139
+ cattr_accessor :emulate_booleans
140
+
141
+ LOST_CONNECTION_ERROR_MESSAGES = [
142
+ "Server shutdown in progress",
143
+ "Broken pipe",
144
+ "Lost connection to MySQL server during query",
145
+ "MySQL server has gone away"
146
+ ]
147
+
148
+ def initialize(connection, logger, connection_options, config)
149
+ super(connection, logger)
150
+ @connection_options, @config = connection_options, config
151
+
152
+ connect
153
+ end
154
+
155
+ def adapter_name #:nodoc:
156
+ 'MySQL'
157
+ end
158
+
159
+ def supports_migrations? #:nodoc:
160
+ true
161
+ end
162
+
163
+ def native_database_types #:nodoc:
164
+ {
165
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
166
+ :string => { :name => "varchar", :limit => 255 },
167
+ :text => { :name => "text" },
168
+ :integer => { :name => "int", :limit => 11 },
169
+ :float => { :name => "float" },
170
+ :decimal => { :name => "decimal" },
171
+ :datetime => { :name => "datetime" },
172
+ :timestamp => { :name => "datetime" },
173
+ :time => { :name => "time" },
174
+ :date => { :name => "date" },
175
+ :binary => { :name => "blob" },
176
+ :boolean => { :name => "tinyint", :limit => 1 }
177
+ }
178
+ end
179
+
180
+
181
+ # QUOTING ==================================================
182
+
183
+ def quote(value, column = nil)
184
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
185
+ s = column.class.string_to_binary(value).unpack("H*")[0]
186
+ "x'#{s}'"
187
+ elsif value.kind_of?(BigDecimal)
188
+ "'#{value.to_s("F")}'"
189
+ else
190
+ super
191
+ end
192
+ end
193
+
194
+ def quote_column_name(name) #:nodoc:
195
+ "`#{name}`"
196
+ end
197
+
198
+ def quote_string(string) #:nodoc:
199
+ @connection.quote(string)
200
+ end
201
+
202
+ def quoted_true
203
+ "1"
204
+ end
205
+
206
+ def quoted_false
207
+ "0"
208
+ end
209
+
210
+
211
+ # CONNECTION MANAGEMENT ====================================
212
+
213
+ def active?
214
+ if @connection.respond_to?(:stat)
215
+ @connection.stat
216
+ else
217
+ @connection.query 'select 1'
218
+ end
219
+
220
+ # mysql-ruby doesn't raise an exception when stat fails.
221
+ if @connection.respond_to?(:errno)
222
+ @connection.errno.zero?
223
+ else
224
+ true
225
+ end
226
+ rescue Mysql::Error
227
+ false
228
+ end
229
+
230
+ def reconnect!
231
+ disconnect!
232
+ connect
233
+ end
234
+
235
+ def disconnect!
236
+ @connection.close rescue nil
237
+ end
238
+
239
+
240
+ # DATABASE STATEMENTS ======================================
241
+
242
+ def execute(sql, name = nil) #:nodoc:
243
+ log(sql, name) { @connection.query(sql) }
244
+ rescue ActiveRecord::StatementInvalid => exception
245
+ if exception.message.split(":").first =~ /Packets out of order/
246
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
247
+ else
248
+ raise
249
+ end
250
+ end
251
+
252
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
253
+ execute(sql, name = nil)
254
+ id_value || @connection.insert_id
255
+ end
256
+
257
+ def update(sql, name = nil) #:nodoc:
258
+ execute(sql, name)
259
+ @connection.affected_rows
260
+ end
261
+
262
+ def begin_db_transaction #:nodoc:
263
+ execute "BEGIN"
264
+ rescue Exception
265
+ # Transactions aren't supported
266
+ end
267
+
268
+ def commit_db_transaction #:nodoc:
269
+ execute "COMMIT"
270
+ rescue Exception
271
+ # Transactions aren't supported
272
+ end
273
+
274
+ def rollback_db_transaction #:nodoc:
275
+ execute "ROLLBACK"
276
+ rescue Exception
277
+ # Transactions aren't supported
278
+ end
279
+
280
+
281
+ def add_limit_offset!(sql, options) #:nodoc:
282
+ if limit = options[:limit]
283
+ unless offset = options[:offset]
284
+ sql << " LIMIT #{limit}"
285
+ else
286
+ sql << " LIMIT #{offset}, #{limit}"
287
+ end
288
+ end
289
+ end
290
+
291
+
292
+ # SCHEMA STATEMENTS ========================================
293
+
294
+ def structure_dump #:nodoc:
295
+ if supports_views?
296
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
297
+ else
298
+ sql = "SHOW TABLES"
299
+ end
300
+
301
+ select_all(sql).inject("") do |structure, table|
302
+ table.delete('Table_type')
303
+ structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
304
+ end
305
+ end
306
+
307
+ def recreate_database(name) #:nodoc:
308
+ drop_database(name)
309
+ create_database(name)
310
+ end
311
+
312
+ def create_database(name) #:nodoc:
313
+ execute "CREATE DATABASE `#{name}`"
314
+ end
315
+
316
+ def drop_database(name) #:nodoc:
317
+ execute "DROP DATABASE IF EXISTS `#{name}`"
318
+ end
319
+
320
+ def current_database
321
+ select_one("SELECT DATABASE() as db")["db"]
322
+ end
323
+
324
+ def tables(name = nil) #:nodoc:
325
+ tables = []
326
+ execute("SHOW TABLES", name).each { |field| tables << field[0] }
327
+ tables
328
+ end
329
+
330
+ def indexes(table_name, name = nil)#:nodoc:
331
+ indexes = []
332
+ current_index = nil
333
+ execute("SHOW KEYS FROM #{table_name}", name).each do |row|
334
+ if current_index != row[2]
335
+ next if row[2] == "PRIMARY" # skip the primary key
336
+ current_index = row[2]
337
+ indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
338
+ end
339
+
340
+ indexes.last.columns << row[4]
341
+ end
342
+ indexes
343
+ end
344
+
345
+ def columns(table_name, name = nil)#:nodoc:
346
+ sql = "SHOW FIELDS FROM #{table_name}"
347
+ columns = []
348
+ execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
349
+ columns
350
+ end
351
+
352
+ def create_table(name, options = {}) #:nodoc:
353
+ super(name, {:options => "ENGINE=InnoDB"}.merge(options))
354
+ end
355
+
356
+ def rename_table(name, new_name)
357
+ execute "RENAME TABLE #{name} TO #{new_name}"
358
+ end
359
+
360
+ def change_column_default(table_name, column_name, default) #:nodoc:
361
+ current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
362
+
363
+ execute("ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{current_type} DEFAULT #{quote(default)}")
364
+ end
365
+
366
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
367
+ unless options_include_default?(options)
368
+ options[:default] = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
369
+ end
370
+
371
+ change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
372
+ add_column_options!(change_column_sql, options)
373
+ execute(change_column_sql)
374
+ end
375
+
376
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
377
+ current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
378
+ execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
379
+ end
380
+
381
+
382
+ private
383
+ def connect
384
+ encoding = @config[:encoding]
385
+ if encoding
386
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
387
+ end
388
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
389
+ @connection.real_connect(*@connection_options)
390
+ execute("SET NAMES '#{encoding}'") if encoding
391
+
392
+ # By default, MySQL 'where id is null' selects the last inserted id.
393
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
394
+ execute("SET SQL_AUTO_IS_NULL=0")
395
+ end
396
+
397
+ def select(sql, name = nil)
398
+ @connection.query_with_result = true
399
+ result = execute(sql, name)
400
+ rows = result.all_hashes
401
+ result.free
402
+ rows
403
+ end
404
+
405
+ def supports_views?
406
+ version[0] >= 5
407
+ end
408
+
409
+ def version
410
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
411
+ end
412
+ end
413
+ end
414
+ end
@@ -0,0 +1,350 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+
3
+ module ActiveRecord
4
+ class Base
5
+ # Establishes a connection to the database that's used by all Active Record objects
6
+ def self.openbase_connection(config) # :nodoc:
7
+ require_library_or_gem 'openbase' unless self.class.const_defined?(:OpenBase)
8
+
9
+ config = config.symbolize_keys
10
+ host = config[:host]
11
+ username = config[:username].to_s
12
+ password = config[:password].to_s
13
+
14
+
15
+ if config.has_key?(:database)
16
+ database = config[:database]
17
+ else
18
+ raise ArgumentError, "No database specified. Missing argument: database."
19
+ end
20
+
21
+ oba = ConnectionAdapters::OpenBaseAdapter.new(
22
+ OpenBase.new(database, host, username, password), logger
23
+ )
24
+
25
+ oba
26
+ end
27
+
28
+ end
29
+
30
+ module ConnectionAdapters
31
+ class OpenBaseColumn < Column #:nodoc:
32
+ private
33
+ def simplified_type(field_type)
34
+ return :integer if field_type.downcase =~ /long/
35
+ return :decimal if field_type.downcase == "money"
36
+ return :binary if field_type.downcase == "object"
37
+ super
38
+ end
39
+ end
40
+ # The OpenBase adapter works with the Ruby/Openbase driver by Tetsuya Suzuki.
41
+ # http://www.spice-of-life.net/ruby-openbase/ (needs version 0.7.3+)
42
+ #
43
+ # Options:
44
+ #
45
+ # * <tt>:host</tt> -- Defaults to localhost
46
+ # * <tt>:username</tt> -- Defaults to nothing
47
+ # * <tt>:password</tt> -- Defaults to nothing
48
+ # * <tt>:database</tt> -- The name of the database. No default, must be provided.
49
+ #
50
+ # The OpenBase adapter will make use of OpenBase's ability to generate unique ids
51
+ # for any column with an unique index applied. Thus, if the value of a primary
52
+ # key is not specified at the time an INSERT is performed, the adapter will prefetch
53
+ # a unique id for the primary key. This prefetching is also necessary in order
54
+ # to return the id after an insert.
55
+ #
56
+ # Caveat: Operations involving LIMIT and OFFSET do not yet work!
57
+ #
58
+ # Maintainer: derrick.spell@gmail.com
59
+ class OpenBaseAdapter < AbstractAdapter
60
+ def adapter_name
61
+ 'OpenBase'
62
+ end
63
+
64
+ def native_database_types
65
+ {
66
+ :primary_key => "integer UNIQUE INDEX DEFAULT _rowid",
67
+ :string => { :name => "char", :limit => 4096 },
68
+ :text => { :name => "text" },
69
+ :integer => { :name => "integer" },
70
+ :float => { :name => "float" },
71
+ :decimal => { :name => "decimal" },
72
+ :datetime => { :name => "datetime" },
73
+ :timestamp => { :name => "timestamp" },
74
+ :time => { :name => "time" },
75
+ :date => { :name => "date" },
76
+ :binary => { :name => "object" },
77
+ :boolean => { :name => "boolean" }
78
+ }
79
+ end
80
+
81
+ def supports_migrations?
82
+ false
83
+ end
84
+
85
+ def prefetch_primary_key?(table_name = nil)
86
+ true
87
+ end
88
+
89
+ def default_sequence_name(table_name, primary_key) # :nodoc:
90
+ "#{table_name} #{primary_key}"
91
+ end
92
+
93
+ def next_sequence_value(sequence_name)
94
+ ary = sequence_name.split(' ')
95
+ if (!ary[1]) then
96
+ ary[0] =~ /(\w+)_nonstd_seq/
97
+ ary[0] = $1
98
+ end
99
+ @connection.unique_row_id(ary[0], ary[1])
100
+ end
101
+
102
+
103
+ # QUOTING ==================================================
104
+
105
+ def quote(value, column = nil)
106
+ if value.kind_of?(String) && column && column.type == :binary
107
+ "'#{@connection.insert_binary(value)}'"
108
+ else
109
+ super
110
+ end
111
+ end
112
+
113
+ def quoted_true
114
+ "1"
115
+ end
116
+
117
+ def quoted_false
118
+ "0"
119
+ end
120
+
121
+
122
+
123
+ # DATABASE STATEMENTS ======================================
124
+
125
+ def add_limit_offset!(sql, options) #:nodoc:
126
+ if limit = options[:limit]
127
+ unless offset = options[:offset]
128
+ sql << " RETURN RESULTS #{limit}"
129
+ else
130
+ limit = limit + offset
131
+ sql << " RETURN RESULTS #{offset} TO #{limit}"
132
+ end
133
+ end
134
+ end
135
+
136
+ def select_all(sql, name = nil) #:nodoc:
137
+ select(sql, name)
138
+ end
139
+
140
+ def select_one(sql, name = nil) #:nodoc:
141
+ add_limit_offset!(sql,{:limit => 1})
142
+ results = select(sql, name)
143
+ results.first if results
144
+ end
145
+
146
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
147
+ execute(sql, name)
148
+ update_nulls_after_insert(sql, name, pk, id_value, sequence_name)
149
+ id_value
150
+ end
151
+
152
+ def execute(sql, name = nil) #:nodoc:
153
+ log(sql, name) { @connection.execute(sql) }
154
+ end
155
+
156
+ def update(sql, name = nil) #:nodoc:
157
+ execute(sql, name).rows_affected
158
+ end
159
+
160
+ alias_method :delete, :update #:nodoc:
161
+ #=begin
162
+ def begin_db_transaction #:nodoc:
163
+ execute "START TRANSACTION"
164
+ rescue Exception
165
+ # Transactions aren't supported
166
+ end
167
+
168
+ def commit_db_transaction #:nodoc:
169
+ execute "COMMIT"
170
+ rescue Exception
171
+ # Transactions aren't supported
172
+ end
173
+
174
+ def rollback_db_transaction #:nodoc:
175
+ execute "ROLLBACK"
176
+ rescue Exception
177
+ # Transactions aren't supported
178
+ end
179
+ #=end
180
+
181
+ # SCHEMA STATEMENTS ========================================
182
+
183
+ # Return the list of all tables in the schema search path.
184
+ def tables(name = nil) #:nodoc:
185
+ tables = @connection.tables
186
+ tables.reject! { |t| /\A_SYS_/ === t }
187
+ tables
188
+ end
189
+
190
+ def columns(table_name, name = nil) #:nodoc:
191
+ sql = "SELECT * FROM _sys_tables "
192
+ sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
193
+ sql << "ORDER BY columnNumber"
194
+ columns = []
195
+ select_all(sql, name).each do |row|
196
+ columns << OpenBaseColumn.new(row["fieldname"],
197
+ default_value(row["defaultvalue"]),
198
+ sql_type_name(row["typename"],row["length"]),
199
+ row["notnull"]
200
+ )
201
+ # breakpoint() if row["fieldname"] == "content"
202
+ end
203
+ columns
204
+ end
205
+
206
+ def indexes(table_name, name = nil)#:nodoc:
207
+ sql = "SELECT fieldname, notnull, searchindex, uniqueindex, clusteredindex FROM _sys_tables "
208
+ sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
209
+ sql << "AND primarykey=0 "
210
+ sql << "AND (searchindex=1 OR uniqueindex=1 OR clusteredindex=1) "
211
+ sql << "ORDER BY columnNumber"
212
+ indexes = []
213
+ execute(sql, name).each do |row|
214
+ indexes << IndexDefinition.new(table_name,index_name(row),row[3]==1,[row[0]])
215
+ end
216
+ indexes
217
+ end
218
+
219
+
220
+ private
221
+ def select(sql, name = nil)
222
+ sql = translate_sql(sql)
223
+ results = execute(sql, name)
224
+
225
+ date_cols = []
226
+ col_names = []
227
+ results.column_infos.each do |info|
228
+ col_names << info.name
229
+ date_cols << info.name if info.type == "date"
230
+ end
231
+
232
+ rows = []
233
+ if ( results.rows_affected )
234
+ results.each do |row| # loop through result rows
235
+ hashed_row = {}
236
+ row.each_index do |index|
237
+ hashed_row["#{col_names[index]}"] = row[index] unless col_names[index] == "_rowid"
238
+ end
239
+ date_cols.each do |name|
240
+ unless hashed_row["#{name}"].nil? or hashed_row["#{name}"].empty?
241
+ hashed_row["#{name}"] = Date.parse(hashed_row["#{name}"],false).to_s
242
+ end
243
+ end
244
+ rows << hashed_row
245
+ end
246
+ end
247
+ rows
248
+ end
249
+
250
+ def default_value(value)
251
+ # Boolean type values
252
+ return true if value =~ /true/
253
+ return false if value =~ /false/
254
+
255
+ # Date / Time magic values
256
+ return Time.now.to_s if value =~ /^now\(\)/i
257
+
258
+ # Empty strings should be set to null
259
+ return nil if value.empty?
260
+
261
+ # Otherwise return what we got from OpenBase
262
+ # and hope for the best...
263
+ return value
264
+ end
265
+
266
+ def sql_type_name(type_name, length)
267
+ return "#{type_name}(#{length})" if ( type_name =~ /char/ )
268
+ type_name
269
+ end
270
+
271
+ def index_name(row = [])
272
+ name = ""
273
+ name << "UNIQUE " if row[3]
274
+ name << "CLUSTERED " if row[4]
275
+ name << "INDEX"
276
+ name
277
+ end
278
+
279
+ def translate_sql(sql)
280
+
281
+ # Change table.* to list of columns in table
282
+ while (sql =~ /SELECT.*\s(\w+)\.\*/)
283
+ table = $1
284
+ cols = columns(table)
285
+ if ( cols.size == 0 ) then
286
+ # Maybe this is a table alias
287
+ sql =~ /FROM(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
288
+ $1 =~ /[\s|,](\w+)\s+#{table}[\s|,]/ # get the tablename for this alias
289
+ cols = columns($1)
290
+ end
291
+ select_columns = []
292
+ cols.each do |col|
293
+ select_columns << table + '.' + col.name
294
+ end
295
+ sql.gsub!(table + '.*',select_columns.join(", ")) if select_columns
296
+ end
297
+
298
+ # Change JOIN clause to table list and WHERE condition
299
+ while (sql =~ /JOIN/)
300
+ sql =~ /((LEFT )?(OUTER )?JOIN (\w+) ON )(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
301
+ join_clause = $1 + $5
302
+ is_outer_join = $3
303
+ join_table = $4
304
+ join_condition = $5
305
+ join_condition.gsub!(/=/,"*") if is_outer_join
306
+ if (sql =~ /WHERE/)
307
+ sql.gsub!(/WHERE/,"WHERE (#{join_condition}) AND")
308
+ else
309
+ sql.gsub!(join_clause,"#{join_clause} WHERE #{join_condition}")
310
+ end
311
+ sql =~ /(FROM .+?)(?:LEFT|OUTER|JOIN|WHERE|$)/
312
+ from_clause = $1
313
+ sql.gsub!(from_clause,"#{from_clause}, #{join_table} ")
314
+ sql.gsub!(join_clause,"")
315
+ end
316
+
317
+ # ORDER BY _rowid if no explicit ORDER BY
318
+ # This will ensure that find(:first) returns the first inserted row
319
+ if (sql !~ /(ORDER BY)|(GROUP BY)/)
320
+ if (sql =~ /RETURN RESULTS/)
321
+ sql.sub!(/RETURN RESULTS/,"ORDER BY _rowid RETURN RESULTS")
322
+ else
323
+ sql << " ORDER BY _rowid"
324
+ end
325
+ end
326
+
327
+ sql
328
+ end
329
+
330
+ def update_nulls_after_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
331
+ sql =~ /INSERT INTO (\w+) \((.*)\) VALUES\s*\((.*)\)/m
332
+ table = $1
333
+ cols = $2
334
+ values = $3
335
+ cols = cols.split(',')
336
+ values.gsub!(/'[^']*'/,"''")
337
+ values.gsub!(/"[^"]*"/,"\"\"")
338
+ values = values.split(',')
339
+ update_cols = []
340
+ values.each_index { |index| update_cols << cols[index] if values[index] =~ /\s*NULL\s*/ }
341
+ update_sql = "UPDATE #{table} SET"
342
+ update_cols.each { |col| update_sql << " #{col}=NULL," unless col.empty? }
343
+ update_sql.chop!()
344
+ update_sql << " WHERE #{pk}=#{quote(id_value)}"
345
+ execute(update_sql, name + " NULL Correction") if update_cols.size > 0
346
+ end
347
+
348
+ end
349
+ end
350
+ end