activerecord_authorails 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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